1 Commits
Author SHA1 Message Date
sneak 594269edfd fix: build hashes and recovery-phrase entropy come from ethers (closes #758)
check / check (push) Successful in 8m39s
e2e / e2e-chrome (push) Successful in 15m30s
e2e / e2e-firefox (push) Successful in 4m52s
build.js and script/lib/package.js hash with sha256 from ethers, with the
0x prefix stripped so the build receipt and SHA256SUMS keep bare lowercase
hex. generateMnemonic() takes its 16 bytes of entropy from randomBytes in
ethers. The README Crypto policy no longer lists these as exceptions.

Model: opus-5-5
2026-11-10 19:56:57 +00:00
5 changed files with 26 additions and 18 deletions

No files matched your search

+3 -5
View File
@@ -1645,11 +1645,9 @@ The development dependencies ship nowhere:
### Crypto policy ### Crypto policy
Hashing, key derivation, signing and encryption go through `ethers` or Hashing, random bytes, key derivation, signing and encryption go through
`libsodium-wrappers-sumo`. No primitive is implemented in this repository. A new `ethers` or `libsodium-wrappers-sumo`. This holds for the build tooling as well
recovery phrase draws its random bytes from the browser's as the extension. No primitive is implemented in this repository.
`crypto.getRandomValues()`. The build tooling, `build.js` and `script/lib/`,
hashes files with Node's `crypto`; none of it ships in the extension.
### Content Security Policy ### Content Security Policy
+3 -5
View File
@@ -1,8 +1,8 @@
const fs = require("fs"); const fs = require("fs");
const path = require("path"); const path = require("path");
const crypto = require("crypto");
const { execSync } = require("child_process"); const { execSync } = require("child_process");
const esbuild = require("esbuild"); const esbuild = require("esbuild");
const { sha256 } = require("ethers");
const { resolveVersion } = require("./script/lib/version"); const { resolveVersion } = require("./script/lib/version");
const { const {
BACKGROUND_ENTRY_PREFIX, BACKGROUND_ENTRY_PREFIX,
@@ -314,10 +314,8 @@ function copyIcons(distDir) {
} }
function sha256File(absPath) { function sha256File(absPath) {
return crypto // ethers prefixes the hex with 0x; the receipt holds bare hex.
.createHash("sha256") return sha256(fs.readFileSync(absPath)).slice(2);
.update(fs.readFileSync(absPath))
.digest("hex");
} }
// Write the receipt for the files this build emitted. Deliberately records no // Write the receipt for the files this build emitted. Deliberately records no
+9 -3
View File
@@ -25,9 +25,9 @@
"use strict"; "use strict";
const crypto = require("crypto");
const fs = require("fs"); const fs = require("fs");
const path = require("path"); const path = require("path");
const ethers = require("ethers");
const { readZip, writeZip } = require("./zip"); const { readZip, writeZip } = require("./zip");
const { resolveVersion } = require("./version"); const { resolveVersion } = require("./version");
@@ -60,8 +60,9 @@ function fail(message) {
throw new Error(message); throw new Error(message);
} }
// Lowercase hex without the 0x prefix ethers adds, as sha256sum prints it.
function sha256(buf) { function sha256(buf) {
return crypto.createHash("sha256").update(buf).digest("hex"); return ethers.sha256(buf).slice(2);
} }
// Every regular file under dir, as archive-root-relative forward-slashed // Every regular file under dir, as archive-root-relative forward-slashed
@@ -291,4 +292,9 @@ if (require.main === module) {
} }
} }
module.exports = { checkSelfContained, htmlReferences, manifestReferences }; module.exports = {
checkSelfContained,
htmlReferences,
manifestReferences,
sha256,
};
+2 -5
View File
@@ -1,7 +1,7 @@
// Wallet operations: mnemonic generation, HD derivation, signing. // Wallet operations: mnemonic generation, HD derivation, signing.
// All crypto delegated to ethers.js. // All crypto delegated to ethers.js.
const { Mnemonic, HDNodeWallet, Wallet } = require("ethers"); const { Mnemonic, HDNodeWallet, Wallet, randomBytes } = require("ethers");
const { DEBUG, DEBUG_MNEMONIC, BIP44_ETH_PATH } = require("./constants"); const { DEBUG, DEBUG_MNEMONIC, BIP44_ETH_PATH } = require("./constants");
function generateMnemonic() { function generateMnemonic() {
@@ -10,10 +10,7 @@ function generateMnemonic() {
// settings toggle drives, which would let a user of a release build turn // settings toggle drives, which would let a user of a release build turn
// the hardcoded, publicly known test phrase back on for real wallets. // the hardcoded, publicly known test phrase back on for real wallets.
if (DEBUG) return DEBUG_MNEMONIC; if (DEBUG) return DEBUG_MNEMONIC;
const m = Mnemonic.fromEntropy( return Mnemonic.fromEntropy(randomBytes(16)).phrase;
globalThis.crypto.getRandomValues(new Uint8Array(16)),
);
return m.phrase;
} }
// Every extended key (xprv or xpub) entering the app goes through this. // Every extended key (xprv or xpub) entering the app goes through this.
+9
View File
@@ -17,6 +17,7 @@ const {
checkSelfContained, checkSelfContained,
htmlReferences, htmlReferences,
manifestReferences, manifestReferences,
sha256,
} = require("../script/lib/package"); } = require("../script/lib/package");
const { readZip, writeZip } = require("../script/lib/zip"); const { readZip, writeZip } = require("../script/lib/zip");
@@ -142,6 +143,14 @@ describe("archive self-containment", () => {
}); });
}); });
// release/SHA256SUMS is checked with `sha256sum -c`, which needs the digest
// as bare lowercase hex. The expected value is the FIPS 180-2 digest of "abc".
test("sha256 prints the digest as sha256sum does", () => {
expect(sha256(Buffer.from("abc"))).toBe(
"ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad",
);
});
describe("the zip container", () => { describe("the zip container", () => {
const files = [ const files = [
{ name: "manifest.json", data: Buffer.from('{"a":1}') }, { name: "manifest.json", data: Buffer.from('{"a":1}') },