eth_sign is signed as an EIP-191 personal message over its data, which is what the Ethereum JSON-RPC specification defines it to return. Its warning on the signature prompt said the site was asking to sign a raw hash that could drain funds, and the README said the same; neither was true. The warning now says the site used the older eth_sign method and that the data will be signed as a personal message, and the README's SignApproval section says so too. Tests pin that the background reads eth_sign address first and data second, and that the sign screen answers it with the personal message signature over the data. Model: opus-5-5
103 lines
3.8 KiB
JavaScript
103 lines
3.8 KiB
JavaScript
// The sign screen signs an eth_sign request the way it signs personal_sign: as
|
|
// an EIP-191 personal message over the bytes of the data
|
|
// (https://git.eeqj.de/sneak/AutistMask/issues/409). Booted through the real
|
|
// popup entry point as the approval window. The background's half, which reads
|
|
// the address first and the data second and checks the signature, is tested in
|
|
// tests/backgroundApproval.test.js.
|
|
|
|
// Mocked so that no password has to be hashed: the stored secret decrypts to
|
|
// the test key below.
|
|
jest.mock("../src/shared/vault", () => ({
|
|
decryptWithPassword: jest.fn(),
|
|
encryptWithPassword: jest.fn(),
|
|
}));
|
|
|
|
const { Wallet, getBytes } = require("ethers");
|
|
const {
|
|
bootPopup,
|
|
cleanupPopup,
|
|
unversionedValidProfile,
|
|
} = require("./support/popupBoot");
|
|
|
|
// A published test key, never used for real funds.
|
|
const SIGNER_KEY =
|
|
"0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d";
|
|
const signer = new Wallet(SIGNER_KEY);
|
|
// "Hello AutistMask", as the hex a page sends.
|
|
const MESSAGE = "0x48656c6c6f204175746973744d61736b";
|
|
const ORIGIN = "https://dapp.example";
|
|
|
|
afterEach(cleanupPopup);
|
|
|
|
test("the sign screen answers eth_sign with a personal message signature over the data", async () => {
|
|
const sent = [];
|
|
const page = await bootPopup(
|
|
unversionedValidProfile({
|
|
wallets: [
|
|
{
|
|
type: "key",
|
|
name: "Wallet 1",
|
|
encryptedSecret: "encrypted-key",
|
|
addresses: [
|
|
{
|
|
address: signer.address,
|
|
balance: "0.0000",
|
|
tokenBalances: [],
|
|
},
|
|
],
|
|
},
|
|
],
|
|
activeAddress: signer.address,
|
|
allowedSites: { [signer.address]: [ORIGIN] },
|
|
}),
|
|
{
|
|
search: "?approval=approval-1",
|
|
runtime: {
|
|
connect: () => ({ postMessage: () => {} }),
|
|
// Answered as the background answers: with the eth_sign
|
|
// approval this window was opened for, and by handing back
|
|
// the signature the popup sends.
|
|
sendMessage: (msg, reply) => {
|
|
sent.push(msg);
|
|
if (msg.type === "AUTISTMASK_GET_APPROVAL") {
|
|
reply({
|
|
type: "sign",
|
|
origin: ORIGIN,
|
|
isPhishingDomain: false,
|
|
approvedFrom: signer.address,
|
|
signParams: {
|
|
method: "eth_sign",
|
|
message: MESSAGE,
|
|
from: signer.address,
|
|
},
|
|
});
|
|
} else if (msg.type === "AUTISTMASK_SIGN_RESPONSE") {
|
|
reply({ signature: msg.signature });
|
|
} else {
|
|
reply(undefined);
|
|
}
|
|
},
|
|
},
|
|
},
|
|
);
|
|
globalThis.window.close = jest.fn();
|
|
const { decryptWithPassword } = require("../src/shared/vault");
|
|
decryptWithPassword.mockResolvedValue(SIGNER_KEY);
|
|
|
|
expect(page.visibleViews()).toEqual(["approve-sign"]);
|
|
page.node("approve-sign-password").value = "any password";
|
|
await page.click("btn-approve-sign");
|
|
|
|
expect(
|
|
sent.filter((msg) => msg.type === "AUTISTMASK_SIGN_RESPONSE"),
|
|
).toEqual([
|
|
{
|
|
type: "AUTISTMASK_SIGN_RESPONSE",
|
|
id: "approval-1",
|
|
approved: true,
|
|
signature: await signer.signMessage(getBytes(MESSAGE)),
|
|
},
|
|
]);
|
|
expect(globalThis.window.close).toHaveBeenCalled();
|
|
});
|