fix: typed data with a missing field or a bool that is not true or false is refused (closes #552)
check / check (push) Waiting to run
e2e / e2e-chrome (push) Waiting to run
e2e / e2e-firefox (push) Waiting to run

ethers signs a bool field by whether its value is truthy, so the string
"false" was shown as false and signed as true, and a bool field left out
was shown as nothing and signed as false. The sign screen now refuses
typed data whose message leaves out a declared field, at any level of
nesting, or holds a bool that is not true or false: the error line names
the field and Sign is disabled. The background refuses a signature for it
too. Presence is an own-key test, so a field named like a built-in
property such as constructor is not found by accident. signedPrimaryType()
moves to the shared module so the background reads the message as the
same type.

Model: opus-5-5
This commit is contained in:
2026-10-14 19:16:48 +00:00
parent f4551dcb67
commit e74cc1be06
6 changed files with 239 additions and 27 deletions
+9 -15
View File
@@ -27,7 +27,6 @@ const {
Interface,
MaxUint256,
toUtf8String,
TypedDataEncoder,
} = require("ethers");
const { getPrice, formatUsd } = require("../../shared/prices");
const { ERC20_ABI } = require("../../shared/constants");
@@ -48,6 +47,8 @@ const { getSignerForAddress } = require("../../shared/wallet");
const { walletDefect } = require("../../shared/walletDefects");
const {
describeSigningFailure,
signedPrimaryType,
typedDataMessageRefusal,
typedDataChainRefusal,
} = require("../../shared/approvalVerify");
const txStatus = require("./txStatus");
@@ -432,22 +433,12 @@ function markInvisibleCharacters(text) {
);
}
// The type ethers will sign typed data as. ethers does not read the page's
// `primaryType`: it takes the one struct in `types` that no other struct
// refers to. Throws when the types name no such single struct, which ethers
// would refuse to sign as well.
function signedPrimaryType(types) {
const structs = { ...types };
// ethers derives EIP712Domain itself and rejects it as an input.
delete structs.EIP712Domain;
return TypedDataEncoder.getPrimaryType(structs);
}
// Why a signature request cannot be signed, as a sentence for the error line,
// or null when it can. Only typed data is refused: the `primaryType` the page
// states has to be the type ethers will sign, or this screen would name one
// message while another is signed. Its domain may name no chain but the active
// network's.
// message while another is signed. Its message has to hold every declared
// field, with each `bool` true or false, and its domain may name no chain but
// the active network's.
function typedDataRefusal(sp) {
if (sp.method === "personal_sign" || sp.method === "eth_sign") return null;
let data;
@@ -470,7 +461,10 @@ function typedDataRefusal(sp) {
", so it cannot be signed."
);
}
return typedDataChainRefusal(data.domain, currentNetwork());
return (
typedDataMessageRefusal(data.types, signed, data.message, "") ||
typedDataChainRefusal(data.domain, currentNetwork())
);
}
// The largest amount a Permit2 allowance can hold, a uint160. Permit2 treats