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
|
||||
- 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
|
||||
are signed, right-to-left characters included. Each control character, and
|
||||
each character that paints nothing (format characters such as zero-width
|
||||
and bidirectional ones, default-ignorable characters such as variation
|
||||
are signed, right-to-left characters included. Each control character,
|
||||
each line or paragraph separator (U+2028, U+2029; left in the text, a
|
||||
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`
|
||||
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
|
||||
|
||||
@@ -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
|
||||
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
|
||||
to right in byte order, control characters and characters that paint nothing
|
||||
are shown as `U+XXXX` marks, and a message that is not hex by the rule signing
|
||||
reads it with is shown as plain text with "Sign" disabled, since such a
|
||||
message has no bytes to sign.
|
||||
to right in byte order, control characters, line and paragraph separators and
|
||||
characters that paint nothing are shown as `U+XXXX` marks, and a message that
|
||||
is not hex by the rule signing reads it with is shown as plain text with
|
||||
"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
|
||||
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
|
||||
* 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`
|
||||
* 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 {
|
||||
direction: ltr;
|
||||
unicode-bidi: bidi-override;
|
||||
|
||||
@@ -410,12 +410,17 @@ function codePointMark(c) {
|
||||
|
||||
// The text as HTML, with each character that paints nothing (zero-width and
|
||||
// bidirectional characters, variation selectors and Hangul fillers among
|
||||
// them) and each control character shown as a mark. A line feed is shown as
|
||||
// a line break. The marks are plain ASCII, so the second pass leaves them be.
|
||||
// them), each control character and each line or paragraph separator
|
||||
// (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) {
|
||||
return escapeHtml(text)
|
||||
.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
|
||||
|
||||
+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
|
||||
// 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).
|
||||
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", [
|
||||
hexlify(toUtf8Bytes(text)),
|
||||
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);
|
||||
await visible(popup, "#view-approve-sign");
|
||||
|
||||
// The left edge of each character on screen, in byte order. A character
|
||||
// the browser's fonts draw with no width shares its neighbour's edge.
|
||||
const lefts = await popup.evaluate(() => {
|
||||
// The text on screen, marks included, and the left edge of each of its
|
||||
// characters, in byte order. A character the browser's fonts draw with
|
||||
// no width shares its neighbour's edge.
|
||||
const shown = await popup.evaluate(() => {
|
||||
const message = document.getElementById("approve-sign-message");
|
||||
const textNode = message.firstChild;
|
||||
const walker = document.createTreeWalker(message, NodeFilter.SHOW_TEXT);
|
||||
const range = document.createRange();
|
||||
const out = [];
|
||||
for (let i = 0; i < textNode.length; i++) {
|
||||
range.setStart(textNode, i);
|
||||
range.setEnd(textNode, i + 1);
|
||||
out.push(range.getBoundingClientRect().left);
|
||||
let text = "";
|
||||
const lefts = [];
|
||||
for (let node = walker.nextNode(); node; node = walker.nextNode()) {
|
||||
for (let i = 0; i < node.length; i++) {
|
||||
range.setStart(node, i);
|
||||
range.setEnd(node, i + 1);
|
||||
lefts.push(range.getBoundingClientRect().left);
|
||||
}
|
||||
text += node.data;
|
||||
}
|
||||
return out;
|
||||
return { text, lefts };
|
||||
});
|
||||
await clickAndClose(popup, "#btn-reject-sign");
|
||||
await assertUserRejection(
|
||||
@@ -3266,10 +3279,14 @@ test("a personal message is laid out in the order of its bytes (#403)", async (e
|
||||
);
|
||||
|
||||
assert(
|
||||
lefts.length === text.length &&
|
||||
lefts.every((left, i) => i === 0 || left >= lefts[i - 1]),
|
||||
shown.text === "Sign inU+2029Pay 5" + rightToLeft + "00 ETH",
|
||||
"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: " +
|
||||
JSON.stringify(lefts),
|
||||
JSON.stringify(shown.lefts),
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
// 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
|
||||
// text it decodes to with control characters and characters that paint
|
||||
// nothing marked rather than obeyed, laid out in byte order, and a message
|
||||
// that is not hex as plain text that cannot be signed.
|
||||
// text it decodes to with control characters, line and paragraph separators
|
||||
// and characters that paint nothing marked rather than obeyed, laid out in
|
||||
// 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
|
||||
// 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_17 = String.fromCodePoint(0xe0100);
|
||||
const HANGUL_FILLER = String.fromCodePoint(0x3164);
|
||||
const LINE_SEPARATOR = String.fromCodePoint(0x2028);
|
||||
const PARAGRAPH_SEPARATOR = String.fromCodePoint(0x2029);
|
||||
|
||||
function makeElement(id) {
|
||||
const classes = new Set();
|
||||
@@ -153,6 +156,23 @@ test("the message is laid out in byte order", async () => {
|
||||
).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 () => {
|
||||
await openPersonalSign(hexlify(toUtf8Bytes("Sign in\nNonce: 7")));
|
||||
expect(node("approve-sign-message").innerHTML).toBe("Sign in<br>Nonce: 7");
|
||||
|
||||
Reference in New Issue
Block a user