fix: the typed data screen shows only the message fields that are signed (closes #539)
ethers signs only the fields a type declares and drops any other key, but the Message section listed every key the site sent, so an added key read as signed. The Message section now follows the signed type, at every level of nesting and in the type's order. Typed data with other keys is not refused: those keys are listed after the message under "Not part of the signature", by their place in the message. README SignApproval says so. The operator/spender test now also checks the Message section, and new tests cover an added key in a nested struct and in a list item. Model: opus-5-5
This commit is contained in:
@@ -233,6 +233,26 @@ function warningOf(data) {
|
||||
return html.slice(0, html.indexOf(">Domain<"));
|
||||
}
|
||||
|
||||
const NOT_SIGNED = ">Not part of the signature<";
|
||||
|
||||
// The Message section alone, without the keys listed after it as not part of
|
||||
// the signature.
|
||||
function messageOf(data) {
|
||||
const html = formatTypedDataHtml(JSON.stringify(data));
|
||||
const end = html.indexOf(NOT_SIGNED);
|
||||
return html.slice(
|
||||
html.indexOf(">Message<"),
|
||||
end === -1 ? html.length : end,
|
||||
);
|
||||
}
|
||||
|
||||
// The keys listed as not part of the signature, or "" when there are none.
|
||||
function notSignedOf(data) {
|
||||
const html = formatTypedDataHtml(JSON.stringify(data));
|
||||
const start = html.indexOf(NOT_SIGNED);
|
||||
return start === -1 ? "" : html.slice(start);
|
||||
}
|
||||
|
||||
function request(data) {
|
||||
return { method: "eth_signTypedData_v4", typedData: JSON.stringify(data) };
|
||||
}
|
||||
@@ -312,7 +332,8 @@ describe("the warning reads only the fields the signed type declares", () => {
|
||||
});
|
||||
|
||||
// A Permit whose type names its spender `operator`; the page adds a
|
||||
// `spender` key the type does not declare.
|
||||
// `spender` key the type does not declare. The Message section names
|
||||
// only the operator, and the added key is listed apart as not signed.
|
||||
test("an added key is not named as the spender", () => {
|
||||
const data = {
|
||||
...PERMIT,
|
||||
@@ -331,6 +352,10 @@ describe("the warning reads only the fields the signed type declares", () => {
|
||||
const warning = warningOf(data);
|
||||
expect(warning).toContain(WARNING);
|
||||
expect(warning).not.toContain(DECOY);
|
||||
expect(messageOf(data)).toContain(SPENDER);
|
||||
expect(messageOf(data)).not.toContain(DECOY);
|
||||
expect(notSignedOf(data)).toContain("spender:");
|
||||
expect(notSignedOf(data)).toContain(DECOY);
|
||||
});
|
||||
|
||||
// The permit for a Uniswap v3 position NFT: a `Permit` with no amount
|
||||
@@ -371,6 +396,47 @@ describe("the warning reads only the fields the signed type declares", () => {
|
||||
});
|
||||
});
|
||||
|
||||
// ethers signs only the declared fields at every level of nesting, so an
|
||||
// added key inside a struct is not signed either. Such typed data is not
|
||||
// refused; the added key is listed apart, with where it sits in the message.
|
||||
describe("the Message section shows only the fields that are signed", () => {
|
||||
const NOTE = "This signature only signs you in";
|
||||
|
||||
test("an added key inside a nested struct is listed as not signed", () => {
|
||||
const data = {
|
||||
...PERMIT_SINGLE,
|
||||
message: {
|
||||
...PERMIT_SINGLE.message,
|
||||
details: { ...PERMIT_SINGLE.message.details, note: NOTE },
|
||||
},
|
||||
};
|
||||
expect(messageOf(data)).toContain("expiration");
|
||||
expect(messageOf(data)).not.toContain(NOTE);
|
||||
expect(notSignedOf(data)).toContain("details.note:");
|
||||
expect(notSignedOf(data)).toContain(NOTE);
|
||||
expect(typedDataRefusal(request(data))).toBeNull();
|
||||
});
|
||||
|
||||
test("an added key inside a list item is listed with its index", () => {
|
||||
const [first, second] = PERMIT_BATCH.message.details;
|
||||
const data = {
|
||||
...PERMIT_BATCH,
|
||||
message: {
|
||||
...PERMIT_BATCH.message,
|
||||
details: [first, { ...second, note: NOTE }],
|
||||
},
|
||||
};
|
||||
expect(messageOf(data)).not.toContain(NOTE);
|
||||
expect(notSignedOf(data)).toContain("details[1].note:");
|
||||
expect(notSignedOf(data)).toContain(NOTE);
|
||||
});
|
||||
|
||||
test("nothing is listed apart when every key is declared", () => {
|
||||
expect(notSignedOf(PERMIT_SINGLE)).toBe("");
|
||||
expect(notSignedOf(LOGIN)).toBe("");
|
||||
});
|
||||
});
|
||||
|
||||
describe("the primary type shown is the one ethers signs", () => {
|
||||
// A drain stated as a sign-in: the page says Login, the types say
|
||||
// PermitSingle, and ethers would sign PermitSingle.
|
||||
|
||||
Reference in New Issue
Block a user