I am attempting to understand how a Chrome extension–based cryptocurrency wallet stores and verifies the seed phrase locally. From an exported keystore (real data from a dummy wallet of course) { "cipher": "aes-128-ctr", "cipherparams": { "iv": "eeebb71712efaa9f6d428a5be3a70c2b" }, "ciphertext": "a220e41509363eb2a69a69de4cae34c87ee57c56bfa7117ac1f4e3b2665ccd313baef8934b46f331bab3e91b18cff57269ebfe2036a9fbf5b62459328f9a3dea8c81eed096bdb80a806110", "kdf": "scrypt", "kdfparams": { "dklen": 32, "n": 16384, "p": 4, "r": 8, "salt": "7e0b6b8681461b10995c8f347010d3ed0fc7c5b4ae4c07682792053c5d538b26" }, "mac": "e84f8c77a491d30769dff6597d1853279355c57494865033105c6dd6cb975d70" } According to the wallet’s source code, Keccak‑256 is used to generate the MAC. I attempted to replicate this using PyCryptodome in Python with the following code, but the computed MAC does not match the stored value: from Crypto.Protocol.KDF import scrypt from Crypto.Hash import keccak data = { ... } ciphertext = bytes.fromhex(data["ciphertext"]) salt = bytes.fromhex(data["kdfparams"]["salt"]) password = "123456789t" derived_key = scrypt(password.encode(), salt=salt, key_len=data["kdfparams"]["dklen"], N=data["kdfparams"]["n"], r=data["kdfparams"]["r"], p=data["kdfparams"]["p"] ) computed_mac = keccak.new(digest_bits=256).update(derived_key[-16:] + ciphertext).hexdigest() print(computed_mac) print(data["mac"]) Computed mac 1cf3468633eaa69c9b66316c5dfae7d43f8d42df204f79406778aafe75fd16e0 Expected mac e84f8c77a491d30769dff6597d1853279355c57494865033105c6dd6cb975d70 Wallet source: https://github.com/trustwallet/wallet-core/blob/master/src/Keystore/EncryptionParameters.cpp#L24 Continue reading...