mirror of
https://github.com/PR0M3TH3AN/SeedPass.git
synced 2025-09-08 07:18:47 +00:00
Merge pull request #813 from PR0M3TH3AN/codex/rename-parameters-and-update-documentation
refactor: rename entropy length parameter
This commit is contained in:
@@ -51,26 +51,34 @@ class BIP85:
|
||||
raise Bip85Error(f"Error initializing BIP32 context: {e}")
|
||||
|
||||
def derive_entropy(
|
||||
self, index: int, bytes_len: int, app_no: int = 39, words_len: int | None = None
|
||||
self,
|
||||
index: int,
|
||||
entropy_bytes: int,
|
||||
app_no: int = 39,
|
||||
word_count: int | None = None,
|
||||
) -> bytes:
|
||||
"""
|
||||
Derives entropy using BIP-85 HMAC-SHA512 method.
|
||||
"""Derive entropy using the BIP-85 HMAC-SHA512 method.
|
||||
|
||||
Parameters:
|
||||
index (int): Index for the child entropy.
|
||||
bytes_len (int): Number of bytes to derive for the entropy.
|
||||
app_no (int): Application number (default 39 for BIP39)
|
||||
entropy_bytes (int): Number of bytes of entropy to derive.
|
||||
app_no (int): Application number (default 39 for BIP39).
|
||||
word_count (int | None): Number of words used in the derivation path
|
||||
for BIP39. If ``None`` and ``app_no`` is ``39``, ``word_count``
|
||||
defaults to ``entropy_bytes``. The final segment of the
|
||||
derivation path becomes ``m/83696968'/39'/0'/word_count'/index'``.
|
||||
|
||||
Returns:
|
||||
bytes: Derived entropy.
|
||||
bytes: Derived entropy of length ``entropy_bytes``.
|
||||
|
||||
Raises:
|
||||
SystemExit: If derivation fails or entropy length is invalid.
|
||||
SystemExit: If derivation fails or the derived entropy length is
|
||||
invalid.
|
||||
"""
|
||||
if app_no == 39:
|
||||
if words_len is None:
|
||||
words_len = bytes_len
|
||||
path = f"m/83696968'/{app_no}'/0'/{words_len}'/{index}'"
|
||||
if word_count is None:
|
||||
word_count = entropy_bytes
|
||||
path = f"m/83696968'/{app_no}'/0'/{word_count}'/{index}'"
|
||||
elif app_no == 32:
|
||||
path = f"m/83696968'/{app_no}'/{index}'"
|
||||
else:
|
||||
@@ -86,17 +94,17 @@ class BIP85:
|
||||
hmac_result = hmac.new(hmac_key, k, hashlib.sha512).digest()
|
||||
logging.debug(f"HMAC-SHA512 result: {hmac_result.hex()}")
|
||||
|
||||
entropy = hmac_result[:bytes_len]
|
||||
entropy = hmac_result[:entropy_bytes]
|
||||
|
||||
if len(entropy) != bytes_len:
|
||||
if len(entropy) != entropy_bytes:
|
||||
logging.error(
|
||||
f"Derived entropy length is {len(entropy)} bytes; expected {bytes_len} bytes."
|
||||
f"Derived entropy length is {len(entropy)} bytes; expected {entropy_bytes} bytes."
|
||||
)
|
||||
print(
|
||||
f"{Fore.RED}Error: Derived entropy length is {len(entropy)} bytes; expected {bytes_len} bytes."
|
||||
f"{Fore.RED}Error: Derived entropy length is {len(entropy)} bytes; expected {entropy_bytes} bytes."
|
||||
)
|
||||
raise Bip85Error(
|
||||
f"Derived entropy length is {len(entropy)} bytes; expected {bytes_len} bytes."
|
||||
f"Derived entropy length is {len(entropy)} bytes; expected {entropy_bytes} bytes."
|
||||
)
|
||||
|
||||
logging.debug(f"Derived entropy: {entropy.hex()}")
|
||||
@@ -107,14 +115,17 @@ class BIP85:
|
||||
raise Bip85Error(f"Error deriving entropy: {e}")
|
||||
|
||||
def derive_mnemonic(self, index: int, words_num: int) -> str:
|
||||
bytes_len = {12: 16, 18: 24, 24: 32}.get(words_num)
|
||||
if not bytes_len:
|
||||
entropy_bytes = {12: 16, 18: 24, 24: 32}.get(words_num)
|
||||
if not entropy_bytes:
|
||||
logging.error(f"Unsupported number of words: {words_num}")
|
||||
print(f"{Fore.RED}Error: Unsupported number of words: {words_num}")
|
||||
raise Bip85Error(f"Unsupported number of words: {words_num}")
|
||||
|
||||
entropy = self.derive_entropy(
|
||||
index=index, bytes_len=bytes_len, app_no=39, words_len=words_num
|
||||
index=index,
|
||||
entropy_bytes=entropy_bytes,
|
||||
app_no=39,
|
||||
word_count=words_num,
|
||||
)
|
||||
try:
|
||||
mnemonic = Bip39MnemonicGenerator(Bip39Languages.ENGLISH).FromEntropy(
|
||||
@@ -130,7 +141,7 @@ class BIP85:
|
||||
def derive_symmetric_key(self, index: int = 0, app_no: int = 2) -> bytes:
|
||||
"""Derive 32 bytes of entropy for symmetric key usage."""
|
||||
try:
|
||||
key = self.derive_entropy(index=index, bytes_len=32, app_no=app_no)
|
||||
key = self.derive_entropy(index=index, entropy_bytes=32, app_no=app_no)
|
||||
logging.debug(f"Derived symmetric key: {key.hex()}")
|
||||
return key
|
||||
except Exception as e:
|
||||
|
@@ -85,7 +85,7 @@ class KeyManager:
|
||||
# Derive entropy for Nostr key (32 bytes)
|
||||
entropy_bytes = self.bip85.derive_entropy(
|
||||
index=index,
|
||||
bytes_len=32,
|
||||
entropy_bytes=32,
|
||||
app_no=NOSTR_KEY_APP_ID,
|
||||
)
|
||||
|
||||
@@ -102,7 +102,7 @@ class KeyManager:
|
||||
"""Derive Nostr keys using the legacy application ID."""
|
||||
try:
|
||||
entropy = self.bip85.derive_entropy(
|
||||
index=0, bytes_len=32, app_no=LEGACY_NOSTR_KEY_APP_ID
|
||||
index=0, entropy_bytes=32, app_no=LEGACY_NOSTR_KEY_APP_ID
|
||||
)
|
||||
return Keys(priv_k=entropy.hex())
|
||||
except Exception as e:
|
||||
|
@@ -461,7 +461,7 @@ class EntryManager:
|
||||
|
||||
seed_bytes = Bip39SeedGenerator(parent_seed).Generate()
|
||||
bip85 = BIP85(seed_bytes)
|
||||
entropy = bip85.derive_entropy(index=index, bytes_len=32)
|
||||
entropy = bip85.derive_entropy(index=index, entropy_bytes=32)
|
||||
keys = Keys(priv_k=entropy.hex())
|
||||
npub = Keys.hex_to_bech32(keys.public_key_hex(), "npub")
|
||||
nsec = Keys.hex_to_bech32(keys.private_key_hex(), "nsec")
|
||||
@@ -539,7 +539,7 @@ class EntryManager:
|
||||
bip85 = BIP85(seed_bytes)
|
||||
|
||||
key_idx = int(entry.get("index", index))
|
||||
entropy = bip85.derive_entropy(index=key_idx, bytes_len=32)
|
||||
entropy = bip85.derive_entropy(index=key_idx, entropy_bytes=32)
|
||||
keys = Keys(priv_k=entropy.hex())
|
||||
npub = Keys.hex_to_bech32(keys.public_key_hex(), "npub")
|
||||
nsec = Keys.hex_to_bech32(keys.private_key_hex(), "nsec")
|
||||
|
@@ -280,13 +280,15 @@ class PasswordManager:
|
||||
)
|
||||
|
||||
@requires_unlocked
|
||||
def get_bip85_entropy(self, purpose: int, index: int, bytes_len: int = 32) -> bytes:
|
||||
def get_bip85_entropy(
|
||||
self, purpose: int, index: int, entropy_bytes: int = 32
|
||||
) -> bytes:
|
||||
"""Return deterministic entropy via the cached BIP-85 function."""
|
||||
|
||||
if self.bip85 is None:
|
||||
raise RuntimeError("BIP-85 is not initialized")
|
||||
return self.bip85.derive_entropy(
|
||||
index=index, bytes_len=bytes_len, app_no=purpose
|
||||
index=index, entropy_bytes=entropy_bytes, app_no=purpose
|
||||
)
|
||||
|
||||
@requires_unlocked
|
||||
@@ -1243,11 +1245,13 @@ class PasswordManager:
|
||||
self._bip85_cache = {}
|
||||
orig_derive = self.bip85.derive_entropy
|
||||
|
||||
def cached_derive(index: int, bytes_len: int, app_no: int = 39) -> bytes:
|
||||
def cached_derive(
|
||||
index: int, entropy_bytes: int, app_no: int = 39
|
||||
) -> bytes:
|
||||
key = (app_no, index)
|
||||
if key not in self._bip85_cache:
|
||||
self._bip85_cache[key] = orig_derive(
|
||||
index=index, bytes_len=bytes_len, app_no=app_no
|
||||
index=index, entropy_bytes=entropy_bytes, app_no=app_no
|
||||
)
|
||||
return self._bip85_cache[key]
|
||||
|
||||
@@ -2727,14 +2731,14 @@ class PasswordManager:
|
||||
from bip_utils import Bip39SeedGenerator
|
||||
|
||||
words = int(entry.get("word_count", entry.get("words", 24)))
|
||||
bytes_len = {12: 16, 18: 24, 24: 32}.get(words, 32)
|
||||
entropy_bytes = {12: 16, 18: 24, 24: 32}.get(words, 32)
|
||||
seed_bytes = Bip39SeedGenerator(self.parent_seed).Generate()
|
||||
bip85 = BIP85(seed_bytes)
|
||||
entropy = bip85.derive_entropy(
|
||||
index=int(entry.get("index", index)),
|
||||
bytes_len=bytes_len,
|
||||
entropy_bytes=entropy_bytes,
|
||||
app_no=39,
|
||||
words_len=words,
|
||||
word_count=words,
|
||||
)
|
||||
print(color_text(f"Entropy: {entropy.hex()}", "deterministic"))
|
||||
except Exception as e: # pragma: no cover - best effort
|
||||
|
@@ -126,7 +126,7 @@ class PasswordGenerator:
|
||||
|
||||
def _derive_password_entropy(self, index: int) -> bytes:
|
||||
"""Derive deterministic entropy for password generation."""
|
||||
entropy = self.bip85.derive_entropy(index=index, bytes_len=64, app_no=32)
|
||||
entropy = self.bip85.derive_entropy(index=index, entropy_bytes=64, app_no=32)
|
||||
logger.debug("Entropy derived for password generation.")
|
||||
|
||||
hkdf = HKDF(
|
||||
@@ -433,7 +433,7 @@ class PasswordGenerator:
|
||||
|
||||
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)
|
||||
return bip85.derive_entropy(index=idx, entropy_bytes=32, app_no=32)
|
||||
|
||||
|
||||
def derive_ssh_key_pair(parent_seed: str, index: int) -> tuple[str, str]:
|
||||
@@ -499,7 +499,7 @@ def derive_pgp_key(
|
||||
import hashlib
|
||||
import datetime
|
||||
|
||||
entropy = bip85.derive_entropy(index=idx, bytes_len=32, app_no=32)
|
||||
entropy = bip85.derive_entropy(index=idx, entropy_bytes=32, app_no=32)
|
||||
created = datetime.datetime(2000, 1, 1, tzinfo=datetime.timezone.utc)
|
||||
|
||||
if key_type.lower() == "rsa":
|
||||
|
@@ -501,8 +501,10 @@ async def test_generate_password_no_special_chars(client):
|
||||
return b"\x00" * 32
|
||||
|
||||
class DummyBIP85:
|
||||
def derive_entropy(self, index: int, bytes_len: int, app_no: int = 32) -> bytes:
|
||||
return bytes(range(bytes_len))
|
||||
def derive_entropy(
|
||||
self, index: int, entropy_bytes: int, app_no: int = 32
|
||||
) -> bytes:
|
||||
return bytes(range(entropy_bytes))
|
||||
|
||||
api.app.state.pm.password_generator = PasswordGenerator(
|
||||
DummyEnc(), "seed", DummyBIP85()
|
||||
@@ -529,8 +531,10 @@ async def test_generate_password_allowed_chars(client):
|
||||
return b"\x00" * 32
|
||||
|
||||
class DummyBIP85:
|
||||
def derive_entropy(self, index: int, bytes_len: int, app_no: int = 32) -> bytes:
|
||||
return bytes((index + i) % 256 for i in range(bytes_len))
|
||||
def derive_entropy(
|
||||
self, index: int, entropy_bytes: int, app_no: int = 32
|
||||
) -> bytes:
|
||||
return bytes((index + i) % 256 for i in range(entropy_bytes))
|
||||
|
||||
api.app.state.pm.password_generator = PasswordGenerator(
|
||||
DummyEnc(), "seed", DummyBIP85()
|
||||
|
52
src/tests/test_bip85_derivation_path.py
Normal file
52
src/tests/test_bip85_derivation_path.py
Normal file
@@ -0,0 +1,52 @@
|
||||
from local_bip85.bip85 import BIP85
|
||||
|
||||
|
||||
class DummyChild:
|
||||
def PrivateKey(self):
|
||||
return self
|
||||
|
||||
def Raw(self):
|
||||
return self
|
||||
|
||||
def ToBytes(self):
|
||||
return b"\x00" * 32
|
||||
|
||||
|
||||
class DummyCtx:
|
||||
def __init__(self):
|
||||
self.last_path = None
|
||||
|
||||
def DerivePath(self, path: str):
|
||||
self.last_path = path
|
||||
return DummyChild()
|
||||
|
||||
|
||||
def test_derivation_paths_for_entropy_lengths():
|
||||
bip85 = BIP85(b"\x00" * 64)
|
||||
ctx = DummyCtx()
|
||||
bip85.bip32_ctx = ctx
|
||||
|
||||
vectors = [
|
||||
(16, 12),
|
||||
(24, 18),
|
||||
(32, 24),
|
||||
]
|
||||
|
||||
for entropy_bytes, word_count in vectors:
|
||||
bip85.derive_entropy(
|
||||
index=0,
|
||||
entropy_bytes=entropy_bytes,
|
||||
app_no=39,
|
||||
word_count=word_count,
|
||||
)
|
||||
assert ctx.last_path == f"m/83696968'/39'/0'/{word_count}'/0'"
|
||||
|
||||
|
||||
def test_default_word_count_from_entropy_bytes():
|
||||
bip85 = BIP85(b"\x00" * 64)
|
||||
ctx = DummyCtx()
|
||||
bip85.bip32_ctx = ctx
|
||||
|
||||
bip85.derive_entropy(index=5, entropy_bytes=20, app_no=39)
|
||||
|
||||
assert ctx.last_path == "m/83696968'/39'/0'/20'/5'"
|
@@ -21,8 +21,8 @@ class DummyEnc:
|
||||
|
||||
|
||||
class DummyBIP85:
|
||||
def derive_entropy(self, index: int, bytes_len: int, app_no: int = 32) -> bytes:
|
||||
return bytes((index + i) % 256 for i in range(bytes_len))
|
||||
def derive_entropy(self, index: int, entropy_bytes: int, app_no: int = 32) -> bytes:
|
||||
return bytes((index + i) % 256 for i in range(entropy_bytes))
|
||||
|
||||
|
||||
def make_manager(tmp_path: Path) -> PasswordManager:
|
||||
|
@@ -13,8 +13,8 @@ class DummyEnc:
|
||||
|
||||
|
||||
class DummyBIP85:
|
||||
def derive_entropy(self, index: int, bytes_len: int, app_no: int = 32) -> bytes:
|
||||
return bytes((index + i) % 256 for i in range(bytes_len))
|
||||
def derive_entropy(self, index: int, entropy_bytes: int, app_no: int = 32) -> bytes:
|
||||
return bytes((index + i) % 256 for i in range(entropy_bytes))
|
||||
|
||||
|
||||
def make_generator(policy=None):
|
||||
|
@@ -8,8 +8,8 @@ class DummyEnc:
|
||||
|
||||
|
||||
class DummyBIP85:
|
||||
def derive_entropy(self, index: int, bytes_len: int, app_no: int = 32) -> bytes:
|
||||
return bytes((index + i) % 256 for i in range(bytes_len))
|
||||
def derive_entropy(self, index: int, entropy_bytes: int, app_no: int = 32) -> bytes:
|
||||
return bytes((index + i) % 256 for i in range(entropy_bytes))
|
||||
|
||||
|
||||
def make_generator():
|
||||
|
@@ -14,8 +14,8 @@ class DummyEnc:
|
||||
|
||||
|
||||
class DummyBIP85:
|
||||
def derive_entropy(self, index: int, bytes_len: int, app_no: int = 32) -> bytes:
|
||||
return bytes((index + i) % 256 for i in range(bytes_len))
|
||||
def derive_entropy(self, index: int, entropy_bytes: int, app_no: int = 32) -> bytes:
|
||||
return bytes((index + i) % 256 for i in range(entropy_bytes))
|
||||
|
||||
|
||||
def make_generator():
|
||||
|
@@ -15,8 +15,8 @@ class DummyEnc:
|
||||
|
||||
|
||||
class DummyBIP85:
|
||||
def derive_entropy(self, index: int, bytes_len: int, app_no: int = 32) -> bytes:
|
||||
return bytes((index + i) % 256 for i in range(bytes_len))
|
||||
def derive_entropy(self, index: int, entropy_bytes: int, app_no: int = 32) -> bytes:
|
||||
return bytes((index + i) % 256 for i in range(entropy_bytes))
|
||||
|
||||
|
||||
def make_generator():
|
||||
|
@@ -12,8 +12,8 @@ class DummyEnc:
|
||||
|
||||
|
||||
class DummyBIP85:
|
||||
def derive_entropy(self, index: int, bytes_len: int, app_no: int = 32) -> bytes:
|
||||
return bytes((index + i) % 256 for i in range(bytes_len))
|
||||
def derive_entropy(self, index: int, entropy_bytes: int, app_no: int = 32) -> bytes:
|
||||
return bytes((index + i) % 256 for i in range(entropy_bytes))
|
||||
|
||||
|
||||
def make_generator():
|
||||
|
@@ -15,8 +15,8 @@ class DummyEnc:
|
||||
|
||||
|
||||
class DummyBIP85:
|
||||
def derive_entropy(self, index: int, bytes_len: int, app_no: int = 32) -> bytes:
|
||||
return bytes((index + i) % 256 for i in range(bytes_len))
|
||||
def derive_entropy(self, index: int, entropy_bytes: int, app_no: int = 32) -> bytes:
|
||||
return bytes((index + i) % 256 for i in range(entropy_bytes))
|
||||
|
||||
|
||||
def make_generator(policy=None):
|
||||
|
@@ -14,8 +14,8 @@ class DummyEnc:
|
||||
|
||||
|
||||
class DummyBIP85:
|
||||
def derive_entropy(self, index: int, bytes_len: int, app_no: int = 32) -> bytes:
|
||||
return bytes((index + i) % 256 for i in range(bytes_len))
|
||||
def derive_entropy(self, index: int, entropy_bytes: int, app_no: int = 32) -> bytes:
|
||||
return bytes((index + i) % 256 for i in range(entropy_bytes))
|
||||
|
||||
|
||||
def make_generator(policy=None):
|
||||
|
@@ -9,10 +9,10 @@ class SlowBIP85:
|
||||
def __init__(self):
|
||||
self.calls = 0
|
||||
|
||||
def derive_entropy(self, index: int, bytes_len: int, app_no: int = 39) -> bytes:
|
||||
def derive_entropy(self, index: int, entropy_bytes: int, app_no: int = 39) -> bytes:
|
||||
self.calls += 1
|
||||
time.sleep(0.01)
|
||||
return b"\x00" * bytes_len
|
||||
return b"\x00" * entropy_bytes
|
||||
|
||||
|
||||
def _setup_manager(bip85: SlowBIP85) -> PasswordManager:
|
||||
@@ -21,10 +21,12 @@ def _setup_manager(bip85: SlowBIP85) -> PasswordManager:
|
||||
pm.bip85 = bip85
|
||||
orig = bip85.derive_entropy
|
||||
|
||||
def cached(index: int, bytes_len: int, app_no: int = 39) -> bytes:
|
||||
def cached(index: int, entropy_bytes: int, app_no: int = 39) -> bytes:
|
||||
key = (app_no, index)
|
||||
if key not in pm._bip85_cache:
|
||||
pm._bip85_cache[key] = orig(index=index, bytes_len=bytes_len, app_no=app_no)
|
||||
pm._bip85_cache[key] = orig(
|
||||
index=index, entropy_bytes=entropy_bytes, app_no=app_no
|
||||
)
|
||||
return pm._bip85_cache[key]
|
||||
|
||||
bip85.derive_entropy = cached
|
||||
|
Reference in New Issue
Block a user