Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ab6e6b3dd9 |
@@ -1929,9 +1929,11 @@ view would leave a wallet one click from deletion.
|
|||||||
- From: color dot + full address + etherscan link
|
- From: color dot + full address + etherscan link
|
||||||
- Message: for `personal_sign` and `eth_sign`, the text the message's bytes
|
- Message: for `personal_sign` and `eth_sign`, the text the message's bytes
|
||||||
decode to as UTF-8, laid out left to right in the order of the bytes that
|
decode to as UTF-8, laid out left to right in the order of the bytes that
|
||||||
are signed, right-to-left characters included. Each control character, and
|
are signed, right-to-left characters included. Each control character,
|
||||||
each character that paints nothing (format characters such as zero-width
|
each line or paragraph separator (U+2028, U+2029; left in the text, a
|
||||||
and bidirectional ones, default-ignorable characters such as variation
|
paragraph separator would end that layout for the text after it), and each
|
||||||
|
character that paints nothing (format characters such as zero-width and
|
||||||
|
bidirectional ones, default-ignorable characters such as variation
|
||||||
selectors and Hangul fillers, and DELETE), is shown as a bordered `U+XXXX`
|
selectors and Hangul fillers, and DELETE), is shown as a bordered `U+XXXX`
|
||||||
mark instead of acting on the text; a line feed is shown as a line break.
|
mark instead of acting on the text; a line feed is shown as a line break.
|
||||||
Bytes that are not UTF-8 are shown as "This message is not text." For
|
Bytes that are not UTF-8 are shown as "This message is not text." For
|
||||||
|
|||||||
@@ -51,10 +51,10 @@ but the review is broader than any of them.
|
|||||||
acting on it, so a site could make the message read differently from what is
|
acting on it, so a site could make the message read differently from what is
|
||||||
signed, and a message that was not hex was shown as NUL characters. The hex is
|
signed, and a message that was not hex was shown as NUL characters. The hex is
|
||||||
now shown as "Raw data" alongside the decoded text, the text is laid out left
|
now shown as "Raw data" alongside the decoded text, the text is laid out left
|
||||||
to right in byte order, control characters and characters that paint nothing
|
to right in byte order, control characters, line and paragraph separators and
|
||||||
are shown as `U+XXXX` marks, and a message that is not hex by the rule signing
|
characters that paint nothing are shown as `U+XXXX` marks, and a message that
|
||||||
reads it with is shown as plain text with "Sign" disabled, since such a
|
is not hex by the rule signing reads it with is shown as plain text with
|
||||||
message has no bytes to sign.
|
"Sign" disabled, since such a message has no bytes to sign.
|
||||||
|
|
||||||
- 2026-10-04: A site has at most one connection prompt and one signature prompt
|
- 2026-10-04: A site has at most one connection prompt and one signature prompt
|
||||||
open at a time ([#405](https://git.eeqj.de/sneak/AutistMask/issues/405)). Each
|
open at a time ([#405](https://git.eeqj.de/sneak/AutistMask/issues/405)). Each
|
||||||
|
|||||||
@@ -68,7 +68,8 @@ body {
|
|||||||
/* A personal message on the signature screen is laid out left to right in
|
/* A personal message on the signature screen is laid out left to right in
|
||||||
* the order of its bytes. Without this, right-to-left characters in it move
|
* the order of its bytes. Without this, right-to-left characters in it move
|
||||||
* the characters around them: `5`, U+05C3, `00` would read as `500`
|
* the characters around them: `5`, U+05C3, `00` would read as `500`
|
||||||
* followed by U+05C3. */
|
* followed by U+05C3. A paragraph separator (U+2029) ends this layout for
|
||||||
|
* the text after it, so src/popup/views/approval.js shows one as a mark. */
|
||||||
.am-byte-order {
|
.am-byte-order {
|
||||||
direction: ltr;
|
direction: ltr;
|
||||||
unicode-bidi: bidi-override;
|
unicode-bidi: bidi-override;
|
||||||
|
|||||||
@@ -410,12 +410,17 @@ function codePointMark(c) {
|
|||||||
|
|
||||||
// The text as HTML, with each character that paints nothing (zero-width and
|
// The text as HTML, with each character that paints nothing (zero-width and
|
||||||
// bidirectional characters, variation selectors and Hangul fillers among
|
// bidirectional characters, variation selectors and Hangul fillers among
|
||||||
// them) and each control character shown as a mark. A line feed is shown as
|
// them), each control character and each line or paragraph separator
|
||||||
// a line break. The marks are plain ASCII, so the second pass leaves them be.
|
// (U+2028, U+2029) shown as a mark. A line feed is shown as a line break.
|
||||||
|
// Left in the text, a paragraph separator would end the byte-order layout
|
||||||
|
// for everything after it. The marks are plain ASCII, so the second pass
|
||||||
|
// leaves them be.
|
||||||
function markInvisibleCharacters(text) {
|
function markInvisibleCharacters(text) {
|
||||||
return escapeHtml(text)
|
return escapeHtml(text)
|
||||||
.replace(INVISIBLE_CHARACTERS, codePointMark)
|
.replace(INVISIBLE_CHARACTERS, codePointMark)
|
||||||
.replace(/\p{Cc}/gu, (c) => (c === "\n" ? "<br>" : codePointMark(c)));
|
.replace(/[\p{Cc}\p{Zl}\p{Zp}]/gu, (c) =>
|
||||||
|
c === "\n" ? "<br>" : codePointMark(c),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// The type ethers will sign typed data as. ethers does not read the page's
|
// The type ethers will sign typed data as. ethers does not read the page's
|
||||||
|
|||||||
+32
-15
@@ -3233,10 +3233,18 @@ test("personal_sign rejected returns a rejection to the page (#183)", async (env
|
|||||||
});
|
});
|
||||||
|
|
||||||
// A right-to-left character must not move the characters around it: U+05C3
|
// A right-to-left character must not move the characters around it: U+05C3
|
||||||
// between "5" and "00" would otherwise put "500" on screen before it
|
// between "5" and "00" would otherwise put "500" on screen before it. A
|
||||||
|
// paragraph separator (U+2029) before it, left in the text, would end the
|
||||||
|
// byte-order layout and bring that back
|
||||||
// (https://git.eeqj.de/sneak/AutistMask/issues/403).
|
// (https://git.eeqj.de/sneak/AutistMask/issues/403).
|
||||||
test("a personal message is laid out in the order of its bytes (#403)", async (env) => {
|
test("a personal message is laid out in the order of its bytes (#403)", async (env) => {
|
||||||
const text = "Pay 5" + String.fromCodePoint(0x05c3) + "00 ETH";
|
const rightToLeft = String.fromCodePoint(0x05c3);
|
||||||
|
const text =
|
||||||
|
"Sign in" +
|
||||||
|
String.fromCodePoint(0x2029) +
|
||||||
|
"Pay 5" +
|
||||||
|
rightToLeft +
|
||||||
|
"00 ETH";
|
||||||
await startRequest(env.dapp, "sign-bidi", "personal_sign", [
|
await startRequest(env.dapp, "sign-bidi", "personal_sign", [
|
||||||
hexlify(toUtf8Bytes(text)),
|
hexlify(toUtf8Bytes(text)),
|
||||||
env.expectedAddress,
|
env.expectedAddress,
|
||||||
@@ -3244,19 +3252,24 @@ test("a personal message is laid out in the order of its bytes (#403)", async (e
|
|||||||
const popup = await waitForApprovalWindow(env.ctx);
|
const popup = await waitForApprovalWindow(env.ctx);
|
||||||
await visible(popup, "#view-approve-sign");
|
await visible(popup, "#view-approve-sign");
|
||||||
|
|
||||||
// The left edge of each character on screen, in byte order. A character
|
// The text on screen, marks included, and the left edge of each of its
|
||||||
// the browser's fonts draw with no width shares its neighbour's edge.
|
// characters, in byte order. A character the browser's fonts draw with
|
||||||
const lefts = await popup.evaluate(() => {
|
// no width shares its neighbour's edge.
|
||||||
|
const shown = await popup.evaluate(() => {
|
||||||
const message = document.getElementById("approve-sign-message");
|
const message = document.getElementById("approve-sign-message");
|
||||||
const textNode = message.firstChild;
|
const walker = document.createTreeWalker(message, NodeFilter.SHOW_TEXT);
|
||||||
const range = document.createRange();
|
const range = document.createRange();
|
||||||
const out = [];
|
let text = "";
|
||||||
for (let i = 0; i < textNode.length; i++) {
|
const lefts = [];
|
||||||
range.setStart(textNode, i);
|
for (let node = walker.nextNode(); node; node = walker.nextNode()) {
|
||||||
range.setEnd(textNode, i + 1);
|
for (let i = 0; i < node.length; i++) {
|
||||||
out.push(range.getBoundingClientRect().left);
|
range.setStart(node, i);
|
||||||
|
range.setEnd(node, i + 1);
|
||||||
|
lefts.push(range.getBoundingClientRect().left);
|
||||||
}
|
}
|
||||||
return out;
|
text += node.data;
|
||||||
|
}
|
||||||
|
return { text, lefts };
|
||||||
});
|
});
|
||||||
await clickAndClose(popup, "#btn-reject-sign");
|
await clickAndClose(popup, "#btn-reject-sign");
|
||||||
await assertUserRejection(
|
await assertUserRejection(
|
||||||
@@ -3266,10 +3279,14 @@ test("a personal message is laid out in the order of its bytes (#403)", async (e
|
|||||||
);
|
);
|
||||||
|
|
||||||
assert(
|
assert(
|
||||||
lefts.length === text.length &&
|
shown.text === "Sign inU+2029Pay 5" + rightToLeft + "00 ETH",
|
||||||
lefts.every((left, i) => i === 0 || left >= lefts[i - 1]),
|
"the paragraph separator is not shown as a mark: " +
|
||||||
|
JSON.stringify(shown.text),
|
||||||
|
);
|
||||||
|
assert(
|
||||||
|
shown.lefts.every((left, i) => i === 0 || left >= shown.lefts[i - 1]),
|
||||||
"the personal message is not laid out in byte order: " +
|
"the personal message is not laid out in byte order: " +
|
||||||
JSON.stringify(lefts),
|
JSON.stringify(shown.lefts),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
// The signature prompt shows a personal message as the bytes that are signed
|
// The signature prompt shows a personal message as the bytes that are signed
|
||||||
// (https://git.eeqj.de/sneak/AutistMask/issues/403): the raw data in hex, the
|
// (https://git.eeqj.de/sneak/AutistMask/issues/403): the raw data in hex, the
|
||||||
// text it decodes to with control characters and characters that paint
|
// text it decodes to with control characters, line and paragraph separators
|
||||||
// nothing marked rather than obeyed, laid out in byte order, and a message
|
// and characters that paint nothing marked rather than obeyed, laid out in
|
||||||
// that is not hex as plain text that cannot be signed.
|
// byte order, and a message that is not hex as plain text that cannot be
|
||||||
|
// signed.
|
||||||
//
|
//
|
||||||
// Driven against a minimal DOM stub in the shape
|
// Driven against a minimal DOM stub in the shape
|
||||||
// tests/approvalOrigin.test.js uses. That the layout keeps right-to-left
|
// tests/approvalOrigin.test.js uses. That the layout keeps right-to-left
|
||||||
@@ -25,6 +26,8 @@ const ZERO_WIDTH_SPACE = String.fromCodePoint(0x200b);
|
|||||||
const VARIATION_SELECTOR_1 = String.fromCodePoint(0xfe00);
|
const VARIATION_SELECTOR_1 = String.fromCodePoint(0xfe00);
|
||||||
const VARIATION_SELECTOR_17 = String.fromCodePoint(0xe0100);
|
const VARIATION_SELECTOR_17 = String.fromCodePoint(0xe0100);
|
||||||
const HANGUL_FILLER = String.fromCodePoint(0x3164);
|
const HANGUL_FILLER = String.fromCodePoint(0x3164);
|
||||||
|
const LINE_SEPARATOR = String.fromCodePoint(0x2028);
|
||||||
|
const PARAGRAPH_SEPARATOR = String.fromCodePoint(0x2029);
|
||||||
|
|
||||||
function makeElement(id) {
|
function makeElement(id) {
|
||||||
const classes = new Set();
|
const classes = new Set();
|
||||||
@@ -153,6 +156,23 @@ test("the message is laid out in byte order", async () => {
|
|||||||
).toBe(true);
|
).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("a control character other than a line feed is marked", async () => {
|
||||||
|
await openPersonalSign(hexlify(toUtf8Bytes("a\u0000b\tc")));
|
||||||
|
expect(shownMessage()).toBe("aU+0000bU+0009c");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("line and paragraph separators are marked", async () => {
|
||||||
|
// Left in the text, a paragraph separator would end the byte-order
|
||||||
|
// layout for everything after it.
|
||||||
|
await openPersonalSign(
|
||||||
|
hexlify(toUtf8Bytes("a" + LINE_SEPARATOR + "b" + PARAGRAPH_SEPARATOR)),
|
||||||
|
);
|
||||||
|
const html = node("approve-sign-message").innerHTML;
|
||||||
|
expect(html).not.toContain(LINE_SEPARATOR);
|
||||||
|
expect(html).not.toContain(PARAGRAPH_SEPARATOR);
|
||||||
|
expect(shownMessage()).toBe("aU+2028bU+2029");
|
||||||
|
});
|
||||||
|
|
||||||
test("a line feed is shown as a line break", async () => {
|
test("a line feed is shown as a line break", async () => {
|
||||||
await openPersonalSign(hexlify(toUtf8Bytes("Sign in\nNonce: 7")));
|
await openPersonalSign(hexlify(toUtf8Bytes("Sign in\nNonce: 7")));
|
||||||
expect(node("approve-sign-message").innerHTML).toBe("Sign in<br>Nonce: 7");
|
expect(node("approve-sign-message").innerHTML).toBe("Sign in<br>Nonce: 7");
|
||||||
|
|||||||
Reference in New Issue
Block a user