mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-11-22 01:47:00 +01:00
Use verifySeedPhrase
from KeyringController
(#19817)
refactor: use verifySeedPhrase from core KeyringController --------- Co-authored-by: Elliot Winkler <elliot.winkler@gmail.com> Co-authored-by: cryptodev-2s <109512101+cryptodev-2s@users.noreply.github.com>
This commit is contained in:
parent
4f14178033
commit
e2be27ed01
@ -1,56 +0,0 @@
|
|||||||
import { KeyringController } from '@metamask/eth-keyring-controller';
|
|
||||||
import log from 'loglevel';
|
|
||||||
|
|
||||||
import { KeyringType } from '../../../shared/constants/keyring';
|
|
||||||
|
|
||||||
const seedPhraseVerifier = {
|
|
||||||
/**
|
|
||||||
* Verifies if the seed words can restore the accounts.
|
|
||||||
*
|
|
||||||
* Key notes:
|
|
||||||
* - The seed words can recreate the primary keyring and the accounts belonging to it.
|
|
||||||
* - The created accounts in the primary keyring are always the same.
|
|
||||||
* - The keyring always creates the accounts in the same sequence.
|
|
||||||
*
|
|
||||||
* @param {Array} createdAccounts - The accounts to restore
|
|
||||||
* @param {Buffer} seedPhrase - The seed words to verify, encoded as a Buffer
|
|
||||||
* @returns {Promise<void>}
|
|
||||||
*/
|
|
||||||
async verifyAccounts(createdAccounts, seedPhrase) {
|
|
||||||
if (!createdAccounts || createdAccounts.length < 1) {
|
|
||||||
throw new Error('No created accounts defined.');
|
|
||||||
}
|
|
||||||
|
|
||||||
const keyringController = new KeyringController({});
|
|
||||||
const keyringBuilder = keyringController.getKeyringBuilderForType(
|
|
||||||
KeyringType.hdKeyTree,
|
|
||||||
);
|
|
||||||
const keyring = keyringBuilder();
|
|
||||||
const opts = {
|
|
||||||
mnemonic: seedPhrase,
|
|
||||||
numberOfAccounts: createdAccounts.length,
|
|
||||||
};
|
|
||||||
|
|
||||||
await keyring.deserialize(opts);
|
|
||||||
const restoredAccounts = await keyring.getAccounts();
|
|
||||||
log.debug(`Created accounts: ${JSON.stringify(createdAccounts)}`);
|
|
||||||
log.debug(`Restored accounts: ${JSON.stringify(restoredAccounts)}`);
|
|
||||||
|
|
||||||
if (restoredAccounts.length !== createdAccounts.length) {
|
|
||||||
// this should not happen...
|
|
||||||
throw new Error('Wrong number of accounts');
|
|
||||||
}
|
|
||||||
|
|
||||||
for (let i = 0; i < restoredAccounts.length; i++) {
|
|
||||||
if (
|
|
||||||
restoredAccounts[i].toLowerCase() !== createdAccounts[i].toLowerCase()
|
|
||||||
) {
|
|
||||||
throw new Error(
|
|
||||||
`Not identical accounts! Original: ${createdAccounts[i]}, Restored: ${restoredAccounts[i]}`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export default seedPhraseVerifier;
|
|
@ -1,121 +0,0 @@
|
|||||||
/**
|
|
||||||
* @jest-environment node
|
|
||||||
* https://github.com/facebook/jest/issues/7780
|
|
||||||
*/
|
|
||||||
import { cloneDeep } from 'lodash';
|
|
||||||
import { KeyringController } from '@metamask/eth-keyring-controller';
|
|
||||||
import firstTimeState from '../first-time-state';
|
|
||||||
import mockEncryptor from '../../../test/lib/mock-encryptor';
|
|
||||||
import { KeyringType } from '../../../shared/constants/keyring';
|
|
||||||
import seedPhraseVerifier from './seed-phrase-verifier';
|
|
||||||
|
|
||||||
describe('SeedPhraseVerifier', () => {
|
|
||||||
describe('verifyAccounts', () => {
|
|
||||||
const password = 'passw0rd1';
|
|
||||||
const { hdKeyTree } = KeyringType;
|
|
||||||
|
|
||||||
let keyringController;
|
|
||||||
let primaryKeyring;
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
|
||||||
keyringController = new KeyringController({
|
|
||||||
initState: cloneDeep(firstTimeState),
|
|
||||||
encryptor: mockEncryptor,
|
|
||||||
});
|
|
||||||
|
|
||||||
expect.any(keyringController);
|
|
||||||
|
|
||||||
await keyringController.createNewVaultAndKeychain(password);
|
|
||||||
primaryKeyring = keyringController.getKeyringsByType(hdKeyTree)[0];
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should be able to verify created account with seed words', async () => {
|
|
||||||
const createdAccounts = await primaryKeyring.getAccounts();
|
|
||||||
expect(createdAccounts).toHaveLength(1);
|
|
||||||
|
|
||||||
const serialized = await primaryKeyring.serialize();
|
|
||||||
const seedWords = serialized.mnemonic;
|
|
||||||
expect(seedWords).not.toHaveLength(0);
|
|
||||||
|
|
||||||
await seedPhraseVerifier.verifyAccounts(createdAccounts, seedWords);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should be able to verify created account (upper case) with seed words', async () => {
|
|
||||||
const createdAccounts = await primaryKeyring.getAccounts();
|
|
||||||
expect(createdAccounts).toHaveLength(1);
|
|
||||||
|
|
||||||
const upperCaseAccounts = [createdAccounts[0].toUpperCase()];
|
|
||||||
|
|
||||||
const serialized = await primaryKeyring.serialize();
|
|
||||||
const seedWords = serialized.mnemonic;
|
|
||||||
expect(seedWords).not.toHaveLength(0);
|
|
||||||
|
|
||||||
await seedPhraseVerifier.verifyAccounts(upperCaseAccounts, seedWords);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should be able to verify created account (lower case) with seed words', async () => {
|
|
||||||
const createdAccounts = await primaryKeyring.getAccounts();
|
|
||||||
expect(createdAccounts).toHaveLength(1);
|
|
||||||
const lowerCaseAccounts = [createdAccounts[0].toLowerCase()];
|
|
||||||
|
|
||||||
const serialized = await primaryKeyring.serialize();
|
|
||||||
const seedWords = serialized.mnemonic;
|
|
||||||
expect(seedWords).not.toHaveLength(0);
|
|
||||||
|
|
||||||
await seedPhraseVerifier.verifyAccounts(lowerCaseAccounts, seedWords);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return error with good but different seed words', async () => {
|
|
||||||
const createdAccounts = await primaryKeyring.getAccounts();
|
|
||||||
expect(createdAccounts).toHaveLength(1);
|
|
||||||
|
|
||||||
await primaryKeyring.serialize();
|
|
||||||
const seedWords =
|
|
||||||
'debris dizzy just program just float decrease vacant alarm reduce speak stadium';
|
|
||||||
|
|
||||||
await expect(async () => {
|
|
||||||
await seedPhraseVerifier.verifyAccounts(createdAccounts, seedWords);
|
|
||||||
}).rejects.toThrow('Not identical accounts!');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return error with undefined existing accounts', async () => {
|
|
||||||
const createdAccounts = await primaryKeyring.getAccounts();
|
|
||||||
expect(createdAccounts).toHaveLength(1);
|
|
||||||
|
|
||||||
await primaryKeyring.serialize();
|
|
||||||
const seedWords =
|
|
||||||
'debris dizzy just program just float decrease vacant alarm reduce speak stadium';
|
|
||||||
|
|
||||||
await expect(async () => {
|
|
||||||
await seedPhraseVerifier.verifyAccounts(undefined, seedWords);
|
|
||||||
}).rejects.toThrow('No created accounts defined.');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return error with empty accounts array', async () => {
|
|
||||||
const createdAccounts = await primaryKeyring.getAccounts();
|
|
||||||
expect(createdAccounts).toHaveLength(1);
|
|
||||||
|
|
||||||
await primaryKeyring.serialize();
|
|
||||||
const seedWords =
|
|
||||||
'debris dizzy just program just float decrease vacant alarm reduce speak stadium';
|
|
||||||
|
|
||||||
await expect(async () => {
|
|
||||||
await seedPhraseVerifier.verifyAccounts([], seedWords);
|
|
||||||
}).rejects.toThrow('No created accounts defined.');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should be able to verify more than one created account with seed words', async () => {
|
|
||||||
await keyringController.addNewAccount(primaryKeyring);
|
|
||||||
await keyringController.addNewAccount(primaryKeyring);
|
|
||||||
|
|
||||||
const createdAccounts = await primaryKeyring.getAccounts();
|
|
||||||
expect(createdAccounts).toHaveLength(3);
|
|
||||||
|
|
||||||
const serialized = await primaryKeyring.serialize();
|
|
||||||
const seedWords = serialized.mnemonic;
|
|
||||||
expect(seedWords).not.toHaveLength(0);
|
|
||||||
|
|
||||||
await seedPhraseVerifier.verifyAccounts(createdAccounts, seedWords);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
@ -194,7 +194,6 @@ import DecryptMessageController from './controllers/decrypt-message';
|
|||||||
import TransactionController from './controllers/transactions';
|
import TransactionController from './controllers/transactions';
|
||||||
import DetectTokensController from './controllers/detect-tokens';
|
import DetectTokensController from './controllers/detect-tokens';
|
||||||
import SwapsController from './controllers/swaps';
|
import SwapsController from './controllers/swaps';
|
||||||
import seedPhraseVerifier from './lib/seed-phrase-verifier';
|
|
||||||
import MetaMetricsController from './controllers/metametrics';
|
import MetaMetricsController from './controllers/metametrics';
|
||||||
import { segment } from './lib/segment';
|
import { segment } from './lib/segment';
|
||||||
import createMetaRPCHandler from './lib/createMetaRPCHandler';
|
import createMetaRPCHandler from './lib/createMetaRPCHandler';
|
||||||
@ -3021,6 +3020,20 @@ export default class MetamaskController extends EventEmitter {
|
|||||||
return new Uint8Array(new Uint16Array(indices).buffer);
|
return new Uint8Array(new Uint16Array(indices).buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts a BIP-39 mnemonic stored as indices of words in the English wordlist to a buffer of Unicode code points.
|
||||||
|
*
|
||||||
|
* @param {Uint8Array} wordlistIndices - Indices to specific words in the BIP-39 English wordlist.
|
||||||
|
* @returns {Buffer} The BIP-39 mnemonic formed from the words in the English wordlist, encoded as a list of Unicode code points.
|
||||||
|
*/
|
||||||
|
_convertEnglishWordlistIndicesToCodepoints(wordlistIndices) {
|
||||||
|
return Buffer.from(
|
||||||
|
Array.from(new Uint16Array(wordlistIndices.buffer))
|
||||||
|
.map((i) => wordlist[i])
|
||||||
|
.join(' '),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get an account balance from the AccountTracker or request it directly from the network.
|
* Get an account balance from the AccountTracker or request it directly from the network.
|
||||||
*
|
*
|
||||||
@ -3461,28 +3474,9 @@ export default class MetamaskController extends EventEmitter {
|
|||||||
* encoded as an array of UTF-8 bytes.
|
* encoded as an array of UTF-8 bytes.
|
||||||
*/
|
*/
|
||||||
async verifySeedPhrase() {
|
async verifySeedPhrase() {
|
||||||
const [primaryKeyring] = this.coreKeyringController.getKeyringsByType(
|
return this._convertEnglishWordlistIndicesToCodepoints(
|
||||||
KeyringType.hdKeyTree,
|
await this.coreKeyringController.verifySeedPhrase(),
|
||||||
);
|
);
|
||||||
if (!primaryKeyring) {
|
|
||||||
throw new Error('MetamaskController - No HD Key Tree found');
|
|
||||||
}
|
|
||||||
|
|
||||||
const serialized = await primaryKeyring.serialize();
|
|
||||||
const seedPhraseAsBuffer = Buffer.from(serialized.mnemonic);
|
|
||||||
|
|
||||||
const accounts = await primaryKeyring.getAccounts();
|
|
||||||
if (accounts.length < 1) {
|
|
||||||
throw new Error('MetamaskController - No accounts found');
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
await seedPhraseVerifier.verifyAccounts(accounts, seedPhraseAsBuffer);
|
|
||||||
return Array.from(seedPhraseAsBuffer.values());
|
|
||||||
} catch (err) {
|
|
||||||
log.error(err.message);
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -871,10 +871,7 @@ describe('MetaMaskController', function () {
|
|||||||
try {
|
try {
|
||||||
await metamaskController.verifySeedPhrase();
|
await metamaskController.verifySeedPhrase();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
assert.equal(
|
assert.equal(error.message, 'No HD keyring found.');
|
||||||
error.message,
|
|
||||||
'MetamaskController - No HD Key Tree found',
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user