def secret_seed(self) -> bytes: self._ensure_shards() selected: Dict[str, Shard] = {} selected_share_id: Optional[int] = None selected_shard_ids: set = set() selected_shards: set = set() satisfied = False filled_groups = set() while not satisfied: shards = [ shard for (name, shard) in self.shards.items() if (name not in selected) and ( selected_share_id is None or shard.share_id == selected_share_id) and ( shard.shard_id not in selected_shard_ids) and ( shard.group_id not in filled_groups) ] if selected_share_id is not None: (enough_shards, _) = check_satisfaction_criteria( selected_shards.union(shards)) if not enough_shards: print([shard.to_str() for shard in shards]) raise HermitError( "There are not enough shards available to unlock this secret." ) name = None try: name = self.interface.choose_shard(shards) except: # catch end-of-input style exceptions pass if name is None: raise HermitError( "Not enough shards selected to unlock secret.") shard = self.shards[name] if selected_share_id is None: selected_share_id = shard.share_id selected_shard_ids.add(shard.shard_id) selected[name] = shard.words() selected_shards.add(shard) (satisfied, filled_groups) = check_satisfaction_criteria(selected_shards) unlocked_mnemonics = list(selected.values()) return shamir_share.combine_mnemonics(unlocked_mnemonics)
def copy_shard(self, original: str, copy: str) -> None: self._ensure_shards() if original not in self.shards: raise HermitError("Shard {} does not exist.".format(original)) if copy in self.shards: err_msg = ( "Shard {} exists. If you need to replace it, delete it first.".format(copy)) raise HermitError(err_msg) original_shard = self.shards[original] copy_shard = Shard( copy, original_shard.encrypted_mnemonic, interface=self.interface) copy_shard.change_password() self.shards[copy] = copy_shard
def _ensure_shards(self, shards_expected: bool = True) -> None: if not self._shards_loaded: bdata = None # If the shards dont exist at the place where they # are expected to by, try to restore them with the externally # configured getPersistedShards command. if not os.path.exists(self.config.shards_file): try: os.system(self.config.commands['getPersistedShards']) except TypeError: pass # If for some reason the persistence layer failed to create the # the shards file, we assume that we just need to initialize # it as an empty bson object. if not os.path.exists(self.config.shards_file): with open(self.config.shards_file, 'wb') as f: f.write(bson.dumps({})) with open(self.config.shards_file, 'rb') as f: bdata = bson.loads(f.read()) for name, shard_bytes in bdata.items(): self.shards[name] = Shard( name, shamir_share.mnemonic_from_bytes(shard_bytes)) self._shards_loaded = True if len(self.shards) == 0 and shards_expected: raise HermitError( "No shards found. Create some by entering 'shards' mode.")
def wrapper(*args, **kwargs): try: return f(*args, **kwargs) except TypeError as terr: raise terr except Exception as err: print(err) raise HermitError("Hmm. Something went wrong.")
def confirm_password(self) -> bytes: password = prompt("new password> ", is_password=True).strip().encode('ascii') confirm = prompt(" confirm> ", is_password=True).strip().encode('ascii') if password == confirm: return password raise HermitError("Passwords do not match.")
def bip32_sequence(bip32_path: str) -> Tuple[int, ...]: """Turn a BIP32 path into a tuple of deriviation points """ bip32_path_regex = "^m(/[0-9]+'?)+$" if not match(bip32_path_regex, bip32_path): raise HermitError("Not a valid BIP32 path.") return tuple( _decode_segment(segment) for segment in bip32_path[2:].split('/') if len(segment) != 0)
def input_shard_words(self, name) -> None: self._ensure_shards(shards_expected=False) if name in self.shards: err_msg = ("Shard exists. If you need to replace it, " + "delete it first.") raise HermitError(err_msg) shard = Shard(name, None, interface=self.interface) shard.input() self.shards[name] = shard
def unlock(self, passphrase: str = "") -> None: if self.root_priv is not None: return mnemonic = Mnemonic(self.language) # TODO skip wallet words words = self.shards.wallet_words() if mnemonic.check(words): seed = Mnemonic.to_seed(words, passphrase=passphrase) self.root_priv = bip32_master_key(seed) else: raise HermitError("Wallet words failed checksum.")
def confirm_password(self) -> bytes: password = prompt("new password> ", is_password=True).strip().encode('ascii') confirm = prompt(" confirm> ", is_password=True).strip().encode('ascii') if password == confirm: # Empty string means do not encrypt if len(password) == 0: return None return password raise HermitError("Passwords do not match.")
def import_shard_qr(self, name: str, shard_data: bytes) -> None: if name in self.shards: err_msg = ("Shard exists. If you need to replace it, " + "delete it first.") raise HermitError(err_msg) shard_dict = bson.loads(shard_data) old_name = list(shard_dict.keys())[0] print_formatted_text( "Importing shard '{}' from qr code as shard '{}'".format(old_name, name)) shard = Shard(name, None, interface=self.interface) shard.from_bytes(shard_dict[old_name]) self.shards[name] = shard
def choose_shard(self, shards) -> Optional[str]: if len(shards) == 0: raise HermitError("Not enough shards to reconstruct secret.") shardnames = [shard.name for shard in shards] shardnames.sort() shardCompleter = WordCompleter(shardnames, sentence=True) while True: prompt_string = "Choose shard\n(options: {} or <enter> to quit)\n> " prompt_msg = prompt_string.format(", ".join(shardnames)) shard_name = prompt(prompt_msg, completer=shardCompleter, **self.options).strip() if shard_name in shardnames: return shard_name if shard_name == '': return None print("Shard not found.")