fix: explain a stored non-master xprv wallet instead of throwing at signing time (closes #234)
All checks were successful
check / check (push) Successful in 30s
All checks were successful
check / check (push) Successful in 30s
This commit was merged in pull request #247.
This commit is contained in:
@@ -120,9 +120,24 @@ function getSignerForAddress(walletData, addrIndex, decryptedSecret) {
|
||||
return node.deriveChild(addrIndex);
|
||||
}
|
||||
if (walletData.type === "xprv") {
|
||||
const node =
|
||||
masterXprvOrThrow(decryptedSecret).derivePath(BIP44_ETH_PATH);
|
||||
return node.deriveChild(addrIndex);
|
||||
// Checked here rather than through masterXprvOrThrow so the message
|
||||
// fits the situation: nobody is importing anything at signing time,
|
||||
// and this wallet is already in storage. src/shared/walletDefects.js
|
||||
// catches it at list-render time; this is the backstop behind that.
|
||||
const node = parseExtendedKey(decryptedSecret);
|
||||
if (!node || !node.privateKey) {
|
||||
throw new Error(
|
||||
"This wallet's stored key is not a valid extended private " +
|
||||
"key, so it cannot sign.",
|
||||
);
|
||||
}
|
||||
if (node.depth !== MASTER_DEPTH) {
|
||||
throw new Error(
|
||||
"This wallet was imported from an extended private key that " +
|
||||
"is not a master key, so it cannot sign.",
|
||||
);
|
||||
}
|
||||
return node.derivePath(BIP44_ETH_PATH).deriveChild(addrIndex);
|
||||
}
|
||||
return new Wallet(decryptedSecret);
|
||||
}
|
||||
@@ -142,6 +157,7 @@ function walletHasRecoveryPhrase(walletData) {
|
||||
|
||||
module.exports = {
|
||||
generateMnemonic,
|
||||
parseExtendedKey,
|
||||
deriveAddressFromXpub,
|
||||
hdWalletFromMnemonic,
|
||||
hdWalletFromXprv,
|
||||
|
||||
86
src/shared/walletDefects.js
Normal file
86
src/shared/walletDefects.js
Normal file
@@ -0,0 +1,86 @@
|
||||
// Wallets already in stored state whose key cannot be used, and the copy that
|
||||
// explains them.
|
||||
//
|
||||
// Refusing a non-master extended private key at import time does nothing for a
|
||||
// wallet imported before that refusal existed. Such a wallet is detected here,
|
||||
// at wallet-list render time, so the user meets the explanation on the list
|
||||
// screen rather than an exception on the send screen. Nothing here modifies or
|
||||
// removes a wallet: the record is the user's data.
|
||||
|
||||
const { parseExtendedKey } = require("./wallet");
|
||||
|
||||
const NON_MASTER_XPRV = "non-master-xprv";
|
||||
|
||||
// An "xprv" wallet stores the neutered BIP-44 Ethereum node, four levels below
|
||||
// the key that was imported: the current import path derives the absolute
|
||||
// m/44'/60'/0'/0 from a depth-0 key, and the pre-#210 path derived the same
|
||||
// four levels as a relative path beneath whatever depth it was given. A master
|
||||
// import therefore stores a depth-4 xpub and a depth-d import stores depth
|
||||
// d + 4, which makes the stored xpub an exact read on the imported key's
|
||||
// depth — and it is readable without the password, unlike the key itself.
|
||||
const BIP44_ETH_XPUB_DEPTH = 4;
|
||||
|
||||
const DEFECTS = {
|
||||
[NON_MASTER_XPRV]: {
|
||||
id: NON_MASTER_XPRV,
|
||||
heading: "This wallet's addresses were derived incorrectly.",
|
||||
paragraphs: [
|
||||
"This wallet was imported from an extended private key that is " +
|
||||
"not a master key. An earlier version applied the Ethereum " +
|
||||
"derivation path beneath that key instead of from a master " +
|
||||
"key, so the addresses listed here are not the ones that key " +
|
||||
"produces under the standard path.",
|
||||
"Signing and sending are disabled for this wallet. The addresses " +
|
||||
"do descend from the extended private key you imported, so " +
|
||||
"anything they hold is still reachable by software that " +
|
||||
"repeats the same non-standard derivation. Check them in a " +
|
||||
"block explorer before deciding what to do.",
|
||||
"To see the addresses this key produces under the standard path, " +
|
||||
"import the master extended private key, or the recovery " +
|
||||
"phrase it came from, as a new wallet. Nothing here has been " +
|
||||
"changed or removed, and this wallet stays until you delete " +
|
||||
"it yourself.",
|
||||
],
|
||||
// One sentence for the places that have room for one: the flash on a
|
||||
// blocked Send, the inline error on the approval screens.
|
||||
shortMessage:
|
||||
"This wallet cannot sign, because it was imported from an " +
|
||||
"extended private key that is not a master key. The wallet list " +
|
||||
"explains what happened.",
|
||||
},
|
||||
};
|
||||
|
||||
// The defect record for a wallet, or null if there is nothing wrong with it
|
||||
// that this module can see. Read-only.
|
||||
//
|
||||
// A wallet whose xpub will not parse gets null rather than a defect: there is
|
||||
// no basis in that case to tell the user their key was not a master key, and a
|
||||
// wrong explanation is worse than none.
|
||||
function walletDefect(walletData) {
|
||||
if (!walletData || walletData.type !== "xprv") return null;
|
||||
const node = parseExtendedKey(walletData.xpub);
|
||||
if (!node) return null;
|
||||
if (node.depth === BIP44_ETH_XPUB_DEPTH) return null;
|
||||
return DEFECTS[NON_MASTER_XPRV];
|
||||
}
|
||||
|
||||
// The notice block for the wallet list, or "" for a wallet with no defect.
|
||||
// The copy is fixed text from this module, so it needs no escaping.
|
||||
function walletDefectHtml(walletData) {
|
||||
const defect = walletDefect(walletData);
|
||||
if (!defect) return "";
|
||||
let html =
|
||||
'<div class="border border-red-500 border-dashed p-2 my-1 text-xs text-red-500">';
|
||||
html += `<div class="font-bold mb-1">${defect.heading}</div>`;
|
||||
for (const p of defect.paragraphs) {
|
||||
html += `<p class="mb-1">${p}</p>`;
|
||||
}
|
||||
html += "</div>";
|
||||
return html;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
NON_MASTER_XPRV,
|
||||
walletDefect,
|
||||
walletDefectHtml,
|
||||
};
|
||||
Reference in New Issue
Block a user