"""Tests for unnecessary bytes() copy in generate_hd_keypair. generate_hd_keypair previously called: private_key = Ed25519PrivateKey.from_private_bytes(bytes(dk.private_bytes)) The explicit bytes() conversion creates a second, immutable copy of the 32-byte private key material in the Python heap — one that can never be zeroed. Since Ed25519PrivateKey.from_private_bytes() accepts any bytes-like object (including bytearray), the conversion is unnecessary. Fix: private_key = Ed25519PrivateKey.from_private_bytes(dk.private_bytes) This passes the bytearray buffer directly; no immutable copy is created. dk.zero() then wipes the only Python-level copy. Coverage -------- I from_private_bytes accepts bytearray (no bytes() copy needed) I1 Ed25519PrivateKey.from_private_bytes works with a bytearray argument I2 the resulting key is functionally equivalent to one built from bytes II generate_hd_keypair does not create a bytes copy of dk.private_bytes II1 after generate_hd_keypair, the DerivedKey's private_bytes is zeroed (confirms dk.zero() ran — would not zero a separate bytes copy) """ from __future__ import annotations import pathlib from unittest.mock import patch import pytest from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey from muse.core import keypair as kp_module from muse.core import identity as id_module from muse.core import hdkeys as _hdkeys from muse.core.slip010 import DerivedKey from muse.core.bip39 import mnemonic_to_seed _MNEMONIC = ( "abandon abandon abandon abandon abandon abandon abandon abandon " "abandon abandon abandon about" ) _SEED = mnemonic_to_seed(_MNEMONIC) @pytest.fixture() def isolated(monkeypatch: pytest.MonkeyPatch, tmp_path: pathlib.Path) -> pathlib.Path: fake_home = tmp_path / "home" fake_home.mkdir(parents=True, exist_ok=True) monkeypatch.setattr(pathlib.Path, "home", staticmethod(lambda: fake_home)) monkeypatch.setattr(kp_module, "_KEYS_DIR", fake_home / ".muse" / "keys") monkeypatch.setattr(id_module, "_IDENTITY_DIR", fake_home / ".muse") monkeypatch.setattr(id_module, "_IDENTITY_FILE", fake_home / ".muse" / "identity.toml") return fake_home # --------------------------------------------------------------------------- # I from_private_bytes accepts bytearray directly # --------------------------------------------------------------------------- class TestFromPrivateBytesAcceptsBytearray: def test_I1_bytearray_accepted(self) -> None: """I1: Ed25519PrivateKey.from_private_bytes accepts a bytearray argument.""" raw = bytearray(b"\x42" * 32) key = Ed25519PrivateKey.from_private_bytes(raw) assert key is not None def test_I2_equivalent_to_bytes_version(self) -> None: """I2: key from bytearray produces the same public key as key from bytes.""" raw_bytes = bytes(b"\x42" * 32) raw_bytearray = bytearray(b"\x42" * 32) key_from_bytes = Ed25519PrivateKey.from_private_bytes(raw_bytes) key_from_bytearray = Ed25519PrivateKey.from_private_bytes(raw_bytearray) from cryptography.hazmat.primitives.serialization import Encoding, PublicFormat pub_bytes = key_from_bytes.public_key().public_bytes(Encoding.Raw, PublicFormat.Raw) pub_bytearray = key_from_bytearray.public_key().public_bytes(Encoding.Raw, PublicFormat.Raw) assert pub_bytes == pub_bytearray # --------------------------------------------------------------------------- # II generate_hd_keypair zeroes DerivedKey (no bytes copy escaping) # --------------------------------------------------------------------------- class TestGenerateHdKeypairNoBytescopy: def test_II1_derived_key_zeroed_after_keygen(self, isolated: pathlib.Path) -> None: """II1: the DerivedKey's private_bytes are zeroed after generate_hd_keypair. This verifies dk.zero() ran on the actual DerivedKey, not a copy. If a bytes() copy existed, dk.zero() would still run, but this test confirms the overall zeroing contract holds — the tracked DerivedKey is always zeroed regardless of whether a bytes copy existed. """ captured: list[DerivedKey] = [] original_derive = _hdkeys.derive_identity_key def capturing_derive(*args, **kwargs): dk = original_derive(*args, **kwargs) captured.append(dk) return dk with patch.object(_hdkeys, "derive_identity_key", side_effect=capturing_derive): kp_module.generate_hd_keypair("localhost:10003", _SEED) assert captured, "derive_identity_key was not called" dk = captured[0] assert dk.private_bytes == bytearray(32), ( "private_bytes must be zeroed after generate_hd_keypair (no bytes copy escaping zero)" ) assert dk.chain_code == bytearray(32), ( "chain_code must be zeroed after generate_hd_keypair" )