"""Tests for mnemonic_to_seed memory zeroing. mnemonic_to_seed previously returned immutable ``bytes`` — the 64-byte root seed lingered in the Python heap indefinitely with no way for callers to wipe it. Because the seed is the master secret from which *every* derived key flows, this is a critical residual-memory exposure. Fix: - mnemonic_to_seed now returns bytearray so callers can zero it after use. - All call sites in auth.py zero the seed bytearray immediately after the last use (operator_seed, agent_sub_seed, seed). Coverage -------- I Return type is bytearray I1 mnemonic_to_seed returns bytearray, not bytes II Callers can zero the returned value II1 returned bytearray can be overwritten in-place III Correctness unaffected III1 same inputs → same 64-byte output (determinism preserved) III2 output length is always 64 bytes III3 passphrase variant produces different seed (BIP39 spec) """ from __future__ import annotations import pytest from muse.core.bip39 import mnemonic_to_seed _MNEMONIC = ( "abandon abandon abandon abandon abandon abandon abandon abandon " "abandon abandon abandon about" ) # --------------------------------------------------------------------------- # I Return type is bytearray # --------------------------------------------------------------------------- class TestReturnType: def test_I1_returns_bytearray(self) -> None: """I1: mnemonic_to_seed must return bytearray so callers can zero it.""" result = mnemonic_to_seed(_MNEMONIC) assert isinstance(result, bytearray), ( f"Expected bytearray, got {type(result).__name__}" ) # --------------------------------------------------------------------------- # II Callers can zero the returned value # --------------------------------------------------------------------------- class TestCallerCanZero: def test_II1_returned_bytearray_is_mutable(self) -> None: """II1: the returned bytearray can be overwritten in place.""" result = mnemonic_to_seed(_MNEMONIC) assert any(b != 0 for b in result), "pre-condition: seed must not be all-zero" result[:] = b"\x00" * len(result) assert result == bytearray(64), "caller must be able to zero the returned bytearray" # --------------------------------------------------------------------------- # III Correctness unaffected # --------------------------------------------------------------------------- class TestCorrectness: def test_III1_deterministic(self) -> None: """III1: same inputs always produce the same 64 bytes.""" r1 = mnemonic_to_seed(_MNEMONIC) r2 = mnemonic_to_seed(_MNEMONIC) assert bytes(r1) == bytes(r2), "mnemonic_to_seed must be deterministic" def test_III2_length_64(self) -> None: """III2: output is always exactly 64 bytes.""" result = mnemonic_to_seed(_MNEMONIC) assert len(result) == 64 def test_III3_passphrase_produces_different_seed(self) -> None: """III3: a non-empty passphrase produces a completely different seed (BIP39 spec).""" r1 = mnemonic_to_seed(_MNEMONIC) r2 = mnemonic_to_seed(_MNEMONIC, passphrase="TREZOR") assert bytes(r1) != bytes(r2), ( "BIP39: different passphrases must produce different seeds" )