diff --git a/src/password_manager/password_generation.py b/src/password_manager/password_generation.py index af37684..670416c 100644 --- a/src/password_manager/password_generation.py +++ b/src/password_manager/password_generation.py @@ -19,6 +19,7 @@ import hashlib import string import random import traceback +import base64 from typing import Optional from termcolor import colored from pathlib import Path @@ -332,3 +333,19 @@ class PasswordGenerator: logger.error(f"Error ensuring password complexity: {e}", exc_info=True) print(colored(f"Error: Failed to ensure password complexity: {e}", "red")) raise + + +def derive_totp_secret(bip85: BIP85, idx: int) -> str: + """Derive a TOTP secret for the given index using BIP85.""" + entropy = bip85.derive_entropy(index=idx, bytes_len=10, app_no=2) + return base64.b32encode(entropy).decode("utf-8") + + +def derive_ssh_key(bip85: BIP85, idx: int) -> bytes: + """Derive 32 bytes of entropy suitable for an SSH key.""" + return bip85.derive_entropy(index=idx, bytes_len=32, app_no=32) + + +def derive_seed_phrase(bip85: BIP85, idx: int, words: int = 24) -> str: + """Derive a new BIP39 seed phrase using BIP85.""" + return bip85.derive_mnemonic(index=idx, words_num=words) diff --git a/src/tests/test_bip85_vectors.py b/src/tests/test_bip85_vectors.py index 1ef3a73..21f872b 100644 --- a/src/tests/test_bip85_vectors.py +++ b/src/tests/test_bip85_vectors.py @@ -5,6 +5,11 @@ import pytest sys.path.append(str(Path(__file__).resolve().parents[1])) from local_bip85.bip85 import BIP85, Bip85Error +from password_manager.password_generation import ( + derive_totp_secret, + derive_ssh_key, + derive_seed_phrase, +) MASTER_XPRV = "xprv9s21ZrQH143K2LBWUUQRFXhucrQqBpKdRRxNVq2zBqsx8HVqFk2uYo8kmbaLLHRdqtQpUm98uKfu3vca1LqdGhUtyoFnCNkfmXRyPXLjbKb" @@ -13,6 +18,8 @@ EXPECTED_12 = "girl mad pet galaxy egg matter matrix prison refuse sense ordinar EXPECTED_24 = "puppy ocean match cereal symbol another shed magic wrap hammer bulb intact gadget divorce twin tonight reason outdoor destroy simple truth cigar social volcano" EXPECTED_SYMM_KEY = "7040bb53104f27367f317558e78a994ada7296c6fde36a364e5baf206e502bb1" +EXPECTED_TOTP_SECRET = "OBALWUYQJ4TTM7ZR" +EXPECTED_SSH_KEY = "52405cd0dd21c5be78314a7c1a3c65ffd8d896536cc7dee3157db5824f0c92e2" @pytest.fixture(scope="module") @@ -32,6 +39,18 @@ def test_bip85_symmetric_key(bip85): assert bip85.derive_symmetric_key(index=0).hex() == EXPECTED_SYMM_KEY +def test_derive_totp_secret(bip85): + assert derive_totp_secret(bip85, 0) == EXPECTED_TOTP_SECRET + + +def test_derive_ssh_key(bip85): + assert derive_ssh_key(bip85, 0).hex() == EXPECTED_SSH_KEY + + +def test_derive_seed_phrase(bip85): + assert derive_seed_phrase(bip85, 0) == EXPECTED_24 + + def test_invalid_params(bip85): with pytest.raises(Bip85Error): bip85.derive_mnemonic(index=0, words_num=15)