def derive_sk_from_hd_path(master_sk: PrivateKey, hd_path_root: str) -> Tuple[PrivateKey, str]: """ Derive a private key from the provided HD path. Takes a master key and HD path as input, and returns the derived key and the HD path that was used to derive it. """ from chia.wallet.derive_keys import _derive_path, _derive_path_unhardened class DerivationType(Enum): NONOBSERVER = 0 OBSERVER = 1 path: List[str] = hd_path_root.split("/") if len(path) == 0 or path[0] != "m": raise ValueError("Invalid HD path. Must start with 'm'") path = path[1:] # Skip "m" if len(path) > 0 and path[-1] == "": # remove trailing slash path = path[:-1] index_and_derivation_types: List[Tuple[int, DerivationType]] = [] # Validate path for current_index_str in path: if len(current_index_str) == 0: raise ValueError("Invalid HD path. Empty index") non_observer: bool = current_index_str[-1] == "n" current_index: int = int(current_index_str[:-1]) if non_observer else int(current_index_str) index_and_derivation_types.append( (current_index, DerivationType.NONOBSERVER if non_observer else DerivationType.OBSERVER) ) current_sk: PrivateKey = master_sk # Derive keys along the path for (current_index, derivation_type) in index_and_derivation_types: if derivation_type == DerivationType.NONOBSERVER: current_sk = _derive_path(current_sk, [current_index]) elif derivation_type == DerivationType.OBSERVER: current_sk = _derive_path_unhardened(current_sk, [current_index]) else: raise ValueError(f"Unhandled derivation type: {derivation_type}") return (current_sk, "m/" + "/".join(path) + "/")
def master_sk_to_backup_sk(master: PrivateKey) -> PrivateKey: return _derive_path(master, [12381, 8444, 4, 0])
def master_sk_to_local_sk(master: PrivateKey) -> PrivateKey: return _derive_path(master, [12381, 8444, 3, 0])
def master_sk_to_wallet_sk(master: PrivateKey, index: uint32) -> PrivateKey: return _derive_path(master, [12381, 8444, 2, index])
def master_sk_to_farmer_sk(master: PrivateKey) -> PrivateKey: return _derive_path(master, [12381, 8444, 0, 0])
def derive_child_key( master_sk: PrivateKey, key_type: Optional[str], derive_from_hd_path: Optional[str], index: int, count: int, non_observer_derivation: bool, show_private_keys: bool, show_hd_path: bool, ): """ Derive child keys from the provided master key. """ from chia.wallet.derive_keys import _derive_path, _derive_path_unhardened derivation_root_sk: Optional[PrivateKey] = None hd_path_root: Optional[str] = None current_sk: Optional[PrivateKey] = None # Key type was specified if key_type is not None: path_indices: List[int] = [12381, 8444] path_indices.append( { "farmer": 0, "pool": 1, "wallet": 2, "local": 3, "backup": 4, "singleton": 5, "pool_auth": 6, }[key_type] ) if non_observer_derivation: current_sk = _derive_path(master_sk, path_indices) else: current_sk = _derive_path_unhardened(master_sk, path_indices) derivation_root_sk = current_sk hd_path_root = "m/" for i in path_indices: hd_path_root += f"{i}{'n' if non_observer_derivation else ''}/" # Arbitrary HD path was specified elif derive_from_hd_path is not None: derivation_root_sk, hd_path_root = derive_sk_from_hd_path(master_sk, derive_from_hd_path) # Derive child keys from derivation_root_sk if derivation_root_sk is not None and hd_path_root is not None: for i in range(index, index + count): if non_observer_derivation: sk = _derive_path(derivation_root_sk, [i]) else: sk = _derive_path_unhardened(derivation_root_sk, [i]) hd_path: str = ( " (" + hd_path_root + str(i) + ("n" if non_observer_derivation else "") + ")" if show_hd_path else "" ) key_type_str: Optional[str] if key_type is not None: key_type_str = key_type.capitalize() else: key_type_str = "Non-Observer" if non_observer_derivation else "Observer" print(f"{key_type_str} public key {i}{hd_path}: {sk.get_g1()}") if show_private_keys: print(f"{key_type_str} private key {i}{hd_path}: {private_key_string_repr(sk)}")
def _search_derived( current_sk: PrivateKey, search_terms: Tuple[str, ...], path: str, path_indices: Optional[List[int]], limit: int, non_observer_derivation: bool, show_progress: bool, search_public_key: bool, search_private_key: bool, search_address: bool, ) -> List[str]: # Return a subset of search_terms that were found """ Performs a shallow search of keys derived from the current sk for items matching the provided search terms. """ from chia.wallet.derive_keys import _derive_path, _derive_path_unhardened class DerivedSearchResultType(Enum): PUBLIC_KEY = "public key" PRIVATE_KEY = "private key" WALLET_ADDRESS = "wallet address" remaining_search_terms: Dict[str, None] = dict.fromkeys(search_terms) current_path: str = path current_path_indices: List[int] = path_indices if path_indices is not None else [] found_search_terms: List[str] = [] for index in range(limit): found_items: List[Tuple[str, str, DerivedSearchResultType]] = [] printed_match: bool = False current_index_str = str(index) + ("n" if non_observer_derivation else "") current_path += f"{current_index_str}" current_path_indices.append(index) if show_progress: # Output just the current index e.g. "25" or "25n" sys.stdout.write(f"{current_index_str}") sys.stdout.flush() # Derive the private key if non_observer_derivation: child_sk = _derive_path(current_sk, current_path_indices) else: child_sk = _derive_path_unhardened(current_sk, current_path_indices) child_pk: Optional[G1Element] = None # Public key is needed for searching against wallet addresses or public keys if search_public_key or search_address: child_pk = child_sk.get_g1() address: Optional[str] = None if search_address: # Generate a wallet address using the standard p2_delegated_puzzle_or_hidden_puzzle puzzle # TODO: consider generating addresses using other puzzles address = encode_puzzle_hash(create_puzzlehash_for_pk(child_pk), "xch") for term in remaining_search_terms: found_item: Any = None found_item_type: Optional[DerivedSearchResultType] = None if search_private_key and term in str(child_sk): found_item = private_key_string_repr(child_sk) found_item_type = DerivedSearchResultType.PRIVATE_KEY elif search_public_key and child_pk is not None and term in str(child_pk): found_item = child_pk found_item_type = DerivedSearchResultType.PUBLIC_KEY elif search_address and address is not None and term in address: found_item = address found_item_type = DerivedSearchResultType.WALLET_ADDRESS if found_item is not None and found_item_type is not None: found_items.append((term, found_item, found_item_type)) if len(found_items) > 0 and show_progress: print() for (term, found_item, found_item_type) in found_items: # Update remaining_search_terms and found_search_terms del remaining_search_terms[term] found_search_terms.append(term) print( f"Found {found_item_type.value}: {found_item} (HD path: {current_path})" ) # lgtm [py/clear-text-logging-sensitive-data] printed_match = True if len(remaining_search_terms) == 0: break # Remove the last index from the path current_path = current_path[: -len(str(current_index_str))] current_path_indices = current_path_indices[:-1] if show_progress: if printed_match: # Write the path (without current_index_str) since we printed out a match # e.g. m/12381/8444/2/ sys.stdout.write(f"{current_path}") # lgtm [py/clear-text-logging-sensitive-data] # Remove the last index from the output else: _clear_line_part(len(current_index_str)) return found_search_terms