refactor: rename entropy length parameter

This commit is contained in:
thePR0M3TH3AN
2025-08-12 09:41:37 -04:00
parent 5109f96ce7
commit a21efa91db
16 changed files with 130 additions and 57 deletions

View File

@@ -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:

View File

@@ -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:

View File

@@ -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")

View File

@@ -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

View File

@@ -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":

View File

@@ -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()

View 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'"

View File

@@ -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:

View File

@@ -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):

View File

@@ -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():

View File

@@ -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():

View File

@@ -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():

View File

@@ -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():

View File

@@ -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):

View File

@@ -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):

View File

@@ -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