gabriel / muse public
test_seed_zeroing.py python
87 lines 3.3 KB
Raw
sha256:2eaa5d95f9d9383498e76947410a26e5a3ba23d182f339910c424cf88fad412b fix: try fetch/presign before fetch/mpack to avoid Cloudfla… Sonnet 4.6 patch 7 days ago
1 """Tests for mnemonic_to_seed memory zeroing.
2
3 mnemonic_to_seed previously returned immutable ``bytes`` — the 64-byte root
4 seed lingered in the Python heap indefinitely with no way for callers to wipe
5 it. Because the seed is the master secret from which *every* derived key
6 flows, this is a critical residual-memory exposure.
7
8 Fix:
9 - mnemonic_to_seed now returns bytearray so callers can zero it after use.
10 - All call sites in auth.py zero the seed bytearray immediately after the
11 last use (operator_seed, agent_sub_seed, seed).
12
13 Coverage
14 --------
15 I Return type is bytearray
16 I1 mnemonic_to_seed returns bytearray, not bytes
17
18 II Callers can zero the returned value
19 II1 returned bytearray can be overwritten in-place
20
21 III Correctness unaffected
22 III1 same inputs → same 64-byte output (determinism preserved)
23 III2 output length is always 64 bytes
24 III3 passphrase variant produces different seed (BIP39 spec)
25 """
26
27 from __future__ import annotations
28
29 import pytest
30
31 from muse.core.bip39 import mnemonic_to_seed
32
33 _MNEMONIC = (
34 "abandon abandon abandon abandon abandon abandon abandon abandon "
35 "abandon abandon abandon about"
36 )
37
38
39 # ---------------------------------------------------------------------------
40 # I Return type is bytearray
41 # ---------------------------------------------------------------------------
42
43 class TestReturnType:
44 def test_I1_returns_bytearray(self) -> None:
45 """I1: mnemonic_to_seed must return bytearray so callers can zero it."""
46 result = mnemonic_to_seed(_MNEMONIC)
47 assert isinstance(result, bytearray), (
48 f"Expected bytearray, got {type(result).__name__}"
49 )
50
51
52 # ---------------------------------------------------------------------------
53 # II Callers can zero the returned value
54 # ---------------------------------------------------------------------------
55
56 class TestCallerCanZero:
57 def test_II1_returned_bytearray_is_mutable(self) -> None:
58 """II1: the returned bytearray can be overwritten in place."""
59 result = mnemonic_to_seed(_MNEMONIC)
60 assert any(b != 0 for b in result), "pre-condition: seed must not be all-zero"
61 result[:] = b"\x00" * len(result)
62 assert result == bytearray(64), "caller must be able to zero the returned bytearray"
63
64
65 # ---------------------------------------------------------------------------
66 # III Correctness unaffected
67 # ---------------------------------------------------------------------------
68
69 class TestCorrectness:
70 def test_III1_deterministic(self) -> None:
71 """III1: same inputs always produce the same 64 bytes."""
72 r1 = mnemonic_to_seed(_MNEMONIC)
73 r2 = mnemonic_to_seed(_MNEMONIC)
74 assert bytes(r1) == bytes(r2), "mnemonic_to_seed must be deterministic"
75
76 def test_III2_length_64(self) -> None:
77 """III2: output is always exactly 64 bytes."""
78 result = mnemonic_to_seed(_MNEMONIC)
79 assert len(result) == 64
80
81 def test_III3_passphrase_produces_different_seed(self) -> None:
82 """III3: a non-empty passphrase produces a completely different seed (BIP39 spec)."""
83 r1 = mnemonic_to_seed(_MNEMONIC)
84 r2 = mnemonic_to_seed(_MNEMONIC, passphrase="TREZOR")
85 assert bytes(r1) != bytes(r2), (
86 "BIP39: different passphrases must produce different seeds"
87 )
File History 1 commit
sha256:2eaa5d95f9d9383498e76947410a26e5a3ba23d182f339910c424cf88fad412b fix: try fetch/presign before fetch/mpack to avoid Cloudfla… Sonnet 4.6 patch 7 days ago