Clarify password role, random die, updated wording
All checks were successful
check / check (push) Successful in 14s

- Password help text now explains it encrypts the recovery phrase
  on disk and is not used for address derivation
- Die button generates cryptographically random phrases using
  crypto.getRandomValues(), different each click
- "roll the die for a new one" wording
- README documents full encryption scheme (PBKDF2 + AES-256-GCM)
  and explicitly notes password is not part of BIP-39 derivation
This commit is contained in:
2026-02-25 15:34:33 +07:00
parent 3dbf885951
commit e6d8f6acf4
3 changed files with 170 additions and 26 deletions

View File

@@ -201,12 +201,22 @@ want maximum privacy can point it at their own Ethereum node.
- **No framework**: The popup UI is vanilla JS and HTML. The extension is small - **No framework**: The popup UI is vanilla JS and HTML. The extension is small
enough that a framework adds unnecessary complexity and attack surface. enough that a framework adds unnecessary complexity and attack surface.
- **Encrypted storage**: Seed phrases are encrypted with a user-provided - **Encrypted storage**: Recovery phrases and private keys are encrypted at rest
password using AES-256-GCM before being stored in the extension's local in the extension's local storage. The encryption scheme:
storage. The encryption key is derived from the password using PBKDF2 with a - The user's password is run through PBKDF2-SHA256 (600,000 iterations) with
high iteration count. a random salt to derive a 256-bit encryption key.
- The encryption key + a random IV encrypt the secret material using
AES-256-GCM.
- Stored blob: `{ salt, iv, ciphertext, authTag }`.
- **The password is NOT used in address derivation.** It exists solely to
protect the recovery phrase / private key on disk. Anyone with the
recovery phrase can restore the wallet on any device without this
password. This matches MetaMask's behavior.
- **BIP-39 / BIP-44**: Standard mnemonic generation and HD key derivation - **BIP-39 / BIP-44**: Standard mnemonic generation and HD key derivation
(`m/44'/60'/0'/0/n`) for Ethereum address compatibility. (`m/44'/60'/0'/0/n`) for Ethereum address compatibility. The BIP-39 passphrase
is always empty (matching MetaMask and most wallet software). The user's
password is completely separate and has no effect on which addresses are
generated.
- **EIP-1193 provider**: The content script injects a `window.ethereum` object - **EIP-1193 provider**: The content script injects a `window.ethereum` object
that implements the EIP-1193 provider interface, enabling web3 site that implements the EIP-1193 provider interface, enabling web3 site
connectivity. connectivity.

View File

@@ -56,8 +56,8 @@
Add Wallet Add Wallet
</h1> </h1>
<p class="mb-2"> <p class="mb-2">
Enter your 12 or 24 word recovery phrase below, or press the Enter your 12 or 24 word recovery phrase below, or click the
die to generate a new one. button to roll the die for a new one.
</p> </p>
<div class="mb-1 flex justify-end"> <div class="mb-1 flex justify-end">
<button <button
@@ -88,8 +88,10 @@
<div class="mb-2" id="add-wallet-password-section"> <div class="mb-2" id="add-wallet-password-section">
<label class="block mb-1">Choose a password</label> <label class="block mb-1">Choose a password</label>
<p class="text-xs text-muted mb-1"> <p class="text-xs text-muted mb-1">
This password locks the wallet on this device. It is not This password encrypts your recovery phrase on this
the same as your recovery phrase. device. It does not affect your wallet addresses or
funds — anyone with your recovery phrase can restore
your wallet without this password.
</p> </p>
<input <input
type="password" type="password"
@@ -154,7 +156,9 @@
<div class="mb-2" id="import-key-password-section"> <div class="mb-2" id="import-key-password-section">
<label class="block mb-1">Choose a password</label> <label class="block mb-1">Choose a password</label>
<p class="text-xs text-muted mb-1"> <p class="text-xs text-muted mb-1">
This password locks the wallet on this device. This password encrypts your private key on this device.
Anyone with your private key can access your funds
without this password.
</p> </p>
<input <input
type="password" type="password"

View File

@@ -70,23 +70,153 @@ function makeStubAddress() {
}; };
} }
// Stub wordlist for random phrase generation.
// TODO: replace with real BIP-39 generation via background.
const STUB_WORDLIST = [
"abandon",
"ability",
"able",
"about",
"above",
"absent",
"absorb",
"abstract",
"absurd",
"abuse",
"access",
"accident",
"account",
"accuse",
"achieve",
"acid",
"acoustic",
"acquire",
"across",
"act",
"action",
"actor",
"actual",
"adapt",
"add",
"addict",
"address",
"adjust",
"admit",
"adult",
"advance",
"advice",
"aerobic",
"affair",
"afford",
"afraid",
"again",
"age",
"agent",
"agree",
"ahead",
"aim",
"air",
"airport",
"aisle",
"alarm",
"album",
"alcohol",
"alert",
"alien",
"all",
"alley",
"allow",
"almost",
"alone",
"alpha",
"already",
"also",
"alter",
"always",
"amateur",
"amazing",
"among",
"amount",
"amused",
"analyst",
"anchor",
"ancient",
"anger",
"angle",
"angry",
"animal",
"ankle",
"announce",
"annual",
"another",
"answer",
"antenna",
"antique",
"anxiety",
"any",
"apart",
"apology",
"appear",
"apple",
"approve",
"april",
"arch",
"arctic",
"area",
"arena",
"argue",
"arm",
"armed",
"armor",
"army",
"around",
"arrange",
"arrest",
"arrive",
"arrow",
"art",
"artefact",
"artist",
"artwork",
"ask",
"aspect",
"assault",
"asset",
"assist",
"assume",
"asthma",
"athlete",
"atom",
"attack",
"attend",
"attitude",
"attract",
"auction",
"audit",
"august",
"aunt",
"author",
"auto",
"autumn",
"average",
"avocado",
"avoid",
"awake",
"aware",
"awesome",
"awful",
"awkward",
"axis",
];
function generateStubMnemonic() { function generateStubMnemonic() {
// TODO: replace with real BIP-39 generation via background const phrase = [];
const words = [ for (let i = 0; i < 12; i++) {
"abandon", const bytes = new Uint32Array(1);
"ability", crypto.getRandomValues(bytes);
"able", phrase.push(STUB_WORDLIST[bytes[0] % STUB_WORDLIST.length]);
"about", }
"above", return phrase.join(" ");
"absent",
"absorb",
"abstract",
"absurd",
"abuse",
"access",
"accident",
];
return words.join(" ");
} }
// -- render wallet list on main view -- // -- render wallet list on main view --