mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-11-22 01:47:00 +01:00
Update SRP representation
An array of integers is now used to represent the SRP in three cases: * In the import wallet flow, the UI uses it to pass the user-provided SRP to the background (which converts the array to a buffer). * In the create wallet flow, the UI uses it to retrieve the generated SRP from the background. * When persisting the wallet to state, the background uses it to serialize the SRP. Co-authored-by: Elliot Winkler <elliot.winkler@gmail.com>
This commit is contained in:
parent
7ec8bd49c7
commit
c3feabf4de
@ -11,10 +11,10 @@ const seedPhraseVerifier = {
|
||||
* - The keyring always creates the accounts in the same sequence.
|
||||
*
|
||||
* @param {Array} createdAccounts - The accounts to restore
|
||||
* @param {string} seedWords - The seed words to verify
|
||||
* @returns {Promise<void>} Promises undefined
|
||||
* @param {Buffer} seedPhrase - The seed words to verify, encoded as a Buffer
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async verifyAccounts(createdAccounts, seedWords) {
|
||||
async verifyAccounts(createdAccounts, seedPhrase) {
|
||||
if (!createdAccounts || createdAccounts.length < 1) {
|
||||
throw new Error('No created accounts defined.');
|
||||
}
|
||||
@ -22,7 +22,7 @@ const seedPhraseVerifier = {
|
||||
const keyringController = new KeyringController({});
|
||||
const Keyring = keyringController.getKeyringClassForType('HD Key Tree');
|
||||
const opts = {
|
||||
mnemonic: seedWords,
|
||||
mnemonic: seedPhrase,
|
||||
numberOfAccounts: createdAccounts.length,
|
||||
};
|
||||
|
||||
|
@ -1792,13 +1792,16 @@ export default class MetamaskController extends EventEmitter {
|
||||
* Create a new Vault and restore an existent keyring.
|
||||
*
|
||||
* @param {string} password
|
||||
* @param {string} seed
|
||||
* @param {number[]} encodedSeedPhrase - The seed phrase, encoded as an array
|
||||
* of UTF-8 bytes.
|
||||
*/
|
||||
async createNewVaultAndRestore(password, seed) {
|
||||
async createNewVaultAndRestore(password, encodedSeedPhrase) {
|
||||
const releaseLock = await this.createVaultMutex.acquire();
|
||||
try {
|
||||
let accounts, lastBalance;
|
||||
|
||||
const seedPhraseAsBuffer = Buffer.from(encodedSeedPhrase);
|
||||
|
||||
const { keyringController } = this;
|
||||
|
||||
// clear known identities
|
||||
@ -1819,7 +1822,7 @@ export default class MetamaskController extends EventEmitter {
|
||||
// create new vault
|
||||
const vault = await keyringController.createNewVaultAndRestore(
|
||||
password,
|
||||
seed,
|
||||
seedPhraseAsBuffer,
|
||||
);
|
||||
|
||||
const ethQuery = new EthQuery(this.provider);
|
||||
@ -2279,7 +2282,8 @@ export default class MetamaskController extends EventEmitter {
|
||||
*
|
||||
* Called when the first account is created and on unlocking the vault.
|
||||
*
|
||||
* @returns {Promise<string>} Seed phrase to be confirmed by the user.
|
||||
* @returns {Promise<number[]>} The seed phrase to be confirmed by the user,
|
||||
* encoded as an array of UTF-8 bytes.
|
||||
*/
|
||||
async verifySeedPhrase() {
|
||||
const primaryKeyring = this.keyringController.getKeyringsByType(
|
||||
@ -2290,7 +2294,7 @@ export default class MetamaskController extends EventEmitter {
|
||||
}
|
||||
|
||||
const serialized = await primaryKeyring.serialize();
|
||||
const seedWords = serialized.mnemonic;
|
||||
const seedPhraseAsBuffer = Buffer.from(serialized.mnemonic);
|
||||
|
||||
const accounts = await primaryKeyring.getAccounts();
|
||||
if (accounts.length < 1) {
|
||||
@ -2298,8 +2302,8 @@ export default class MetamaskController extends EventEmitter {
|
||||
}
|
||||
|
||||
try {
|
||||
await seedPhraseVerifier.verifyAccounts(accounts, seedWords);
|
||||
return seedWords;
|
||||
await seedPhraseVerifier.verifyAccounts(accounts, seedPhraseAsBuffer);
|
||||
return Array.from(seedPhraseAsBuffer.values());
|
||||
} catch (err) {
|
||||
log.error(err.message);
|
||||
throw err;
|
||||
|
@ -1175,6 +1175,9 @@
|
||||
}
|
||||
},
|
||||
"bip39": {
|
||||
"globals": {
|
||||
"console.log": true
|
||||
},
|
||||
"packages": {
|
||||
"buffer": true,
|
||||
"create-hash": true,
|
||||
@ -1889,6 +1892,7 @@
|
||||
"eth-hd-keyring": {
|
||||
"packages": {
|
||||
"bip39": true,
|
||||
"buffer": true,
|
||||
"eth-sig-util": true,
|
||||
"eth-simple-keyring": true,
|
||||
"ethereumjs-wallet": true
|
||||
@ -1947,6 +1951,7 @@
|
||||
"packages": {
|
||||
"bip39": true,
|
||||
"browser-passworder": true,
|
||||
"buffer": true,
|
||||
"eth-hd-keyring": true,
|
||||
"eth-sig-util": true,
|
||||
"eth-simple-keyring": true,
|
||||
|
@ -1194,6 +1194,9 @@
|
||||
}
|
||||
},
|
||||
"bip39": {
|
||||
"globals": {
|
||||
"console.log": true
|
||||
},
|
||||
"packages": {
|
||||
"buffer": true,
|
||||
"create-hash": true,
|
||||
@ -1908,6 +1911,7 @@
|
||||
"eth-hd-keyring": {
|
||||
"packages": {
|
||||
"bip39": true,
|
||||
"buffer": true,
|
||||
"eth-sig-util": true,
|
||||
"eth-simple-keyring": true,
|
||||
"ethereumjs-wallet": true
|
||||
@ -1966,6 +1970,7 @@
|
||||
"packages": {
|
||||
"bip39": true,
|
||||
"browser-passworder": true,
|
||||
"buffer": true,
|
||||
"eth-hd-keyring": true,
|
||||
"eth-sig-util": true,
|
||||
"eth-simple-keyring": true,
|
||||
|
@ -1175,6 +1175,9 @@
|
||||
}
|
||||
},
|
||||
"bip39": {
|
||||
"globals": {
|
||||
"console.log": true
|
||||
},
|
||||
"packages": {
|
||||
"buffer": true,
|
||||
"create-hash": true,
|
||||
@ -1889,6 +1892,7 @@
|
||||
"eth-hd-keyring": {
|
||||
"packages": {
|
||||
"bip39": true,
|
||||
"buffer": true,
|
||||
"eth-sig-util": true,
|
||||
"eth-simple-keyring": true,
|
||||
"ethereumjs-wallet": true
|
||||
@ -1947,6 +1951,7 @@
|
||||
"packages": {
|
||||
"bip39": true,
|
||||
"browser-passworder": true,
|
||||
"buffer": true,
|
||||
"eth-hd-keyring": true,
|
||||
"eth-sig-util": true,
|
||||
"eth-simple-keyring": true,
|
||||
|
99
patches/bip39+2.5.0.patch
Normal file
99
patches/bip39+2.5.0.patch
Normal file
@ -0,0 +1,99 @@
|
||||
diff --git a/node_modules/bip39/index.js b/node_modules/bip39/index.js
|
||||
index aa0f29f..bee8008 100644
|
||||
--- a/node_modules/bip39/index.js
|
||||
+++ b/node_modules/bip39/index.js
|
||||
@@ -48,7 +48,9 @@ function salt (password) {
|
||||
}
|
||||
|
||||
function mnemonicToSeed (mnemonic, password) {
|
||||
- var mnemonicBuffer = Buffer.from(unorm.nfkd(mnemonic), 'utf8')
|
||||
+ var mnemonicBuffer = typeof mnemonic === 'string'
|
||||
+ ? Buffer.from(unorm.nfkd(mnemonic), 'utf8')
|
||||
+ : mnemonic
|
||||
var saltBuffer = Buffer.from(salt(unorm.nfkd(password)), 'utf8')
|
||||
|
||||
return pbkdf2(mnemonicBuffer, saltBuffer, 2048, 64, 'sha512')
|
||||
@@ -61,12 +63,28 @@ function mnemonicToSeedHex (mnemonic, password) {
|
||||
function mnemonicToEntropy (mnemonic, wordlist) {
|
||||
wordlist = wordlist || DEFAULT_WORDLIST
|
||||
|
||||
- var words = unorm.nfkd(mnemonic).split(' ')
|
||||
+ var mnemonicAsBuffer = typeof mnemonic === 'string'
|
||||
+ ? Buffer.from(unorm.nfkd(mnemonic), 'utf8')
|
||||
+ : mnemonic
|
||||
+
|
||||
+ var words = [];
|
||||
+ var currentWord = [];
|
||||
+ for (const byte of mnemonicAsBuffer.values()) {
|
||||
+ // split at space or \u3000 (ideographic space, for Japanese wordlists)
|
||||
+ if (byte === 0x20 || byte === 0x3000) {
|
||||
+ words.push(Buffer.from(currentWord));
|
||||
+ currentWord = [];
|
||||
+ } else {
|
||||
+ currentWord.push(byte);
|
||||
+ }
|
||||
+ }
|
||||
+ words.push(Buffer.from(currentWord));
|
||||
+
|
||||
if (words.length % 3 !== 0) throw new Error(INVALID_MNEMONIC)
|
||||
|
||||
// convert word indices to 11 bit binary strings
|
||||
var bits = words.map(function (word) {
|
||||
- var index = wordlist.indexOf(word)
|
||||
+ var index = wordlist.indexOf(word.toString('utf8'))
|
||||
if (index === -1) throw new Error(INVALID_MNEMONIC)
|
||||
|
||||
return lpad(index.toString(2), '0', 11)
|
||||
@@ -104,12 +122,41 @@ function entropyToMnemonic (entropy, wordlist) {
|
||||
|
||||
var bits = entropyBits + checksumBits
|
||||
var chunks = bits.match(/(.{1,11})/g)
|
||||
- var words = chunks.map(function (binary) {
|
||||
+ var wordsAsBuffers = chunks.map(function (binary) {
|
||||
var index = binaryToByte(binary)
|
||||
- return wordlist[index]
|
||||
+ return Buffer.from(wordlist[index], 'utf8')
|
||||
})
|
||||
|
||||
- return wordlist === JAPANESE_WORDLIST ? words.join('\u3000') : words.join(' ')
|
||||
+ var bufferSize = wordsAsBuffers.reduce(function (bufferSize, wordAsBuffer, i) {
|
||||
+ var shouldAddSeparator = i < wordsAsBuffers.length - 1
|
||||
+ return (
|
||||
+ bufferSize +
|
||||
+ wordAsBuffer.length +
|
||||
+ (shouldAddSeparator ? 1 : 0)
|
||||
+ )
|
||||
+ }, 0)
|
||||
+ var separator = wordlist === JAPANESE_WORDLIST ? '\u3000' : ' '
|
||||
+ var result = wordsAsBuffers.reduce(function (result, wordAsBuffer, i) {
|
||||
+ var shouldAddSeparator = i < wordsAsBuffers.length - 1
|
||||
+ result.workingBuffer.set(wordAsBuffer, result.offset)
|
||||
+ if (shouldAddSeparator) {
|
||||
+ result.workingBuffer.write(
|
||||
+ separator,
|
||||
+ result.offset + wordAsBuffer.length,
|
||||
+ separator.length,
|
||||
+ 'utf8'
|
||||
+ )
|
||||
+ }
|
||||
+ return {
|
||||
+ workingBuffer: result.workingBuffer,
|
||||
+ offset: (
|
||||
+ result.offset +
|
||||
+ wordAsBuffer.length +
|
||||
+ (shouldAddSeparator ? 1 : 0)
|
||||
+ )
|
||||
+ }
|
||||
+ }, { workingBuffer: Buffer.alloc(bufferSize), offset: 0 })
|
||||
+ return result.workingBuffer;
|
||||
}
|
||||
|
||||
function generateMnemonic (strength, rng, wordlist) {
|
||||
@@ -124,6 +171,7 @@ function validateMnemonic (mnemonic, wordlist) {
|
||||
try {
|
||||
mnemonicToEntropy(mnemonic, wordlist)
|
||||
} catch (e) {
|
||||
+ console.log('could not validate mnemonic', e)
|
||||
return false
|
||||
}
|
||||
|
43
patches/eth-hd-keyring+3.6.0.patch
Normal file
43
patches/eth-hd-keyring+3.6.0.patch
Normal file
@ -0,0 +1,43 @@
|
||||
diff --git a/node_modules/eth-hd-keyring/index.js b/node_modules/eth-hd-keyring/index.js
|
||||
index 19d1d7f..350d6b8 100644
|
||||
--- a/node_modules/eth-hd-keyring/index.js
|
||||
+++ b/node_modules/eth-hd-keyring/index.js
|
||||
@@ -17,8 +17,11 @@ class HdKeyring extends SimpleKeyring {
|
||||
}
|
||||
|
||||
serialize () {
|
||||
+ const mnemonicAsBuffer = typeof this.mnemonic === 'string'
|
||||
+ ? Buffer.from(this.mnemonic, 'utf8')
|
||||
+ : this.mnemonic
|
||||
return Promise.resolve({
|
||||
- mnemonic: this.mnemonic,
|
||||
+ mnemonic: Array.from(mnemonicAsBuffer.values()),
|
||||
numberOfAccounts: this.wallets.length,
|
||||
hdPath: this.hdPath,
|
||||
})
|
||||
@@ -69,9 +72,22 @@ class HdKeyring extends SimpleKeyring {
|
||||
|
||||
/* PRIVATE METHODS */
|
||||
|
||||
- _initFromMnemonic (mnemonic) {
|
||||
- this.mnemonic = mnemonic
|
||||
- const seed = bip39.mnemonicToSeed(mnemonic)
|
||||
+ /**
|
||||
+ * Sets appropriate properties for the keyring based on the given
|
||||
+ * BIP39-compliant mnemonic.
|
||||
+ *
|
||||
+ * @param {string|Array<number>|Buffer} mnemonic - A seed phrase represented
|
||||
+ * as a string, an array of UTF-8 bytes, or a Buffer.
|
||||
+ */
|
||||
+ _initFromMnemonic(mnemonic) {
|
||||
+ if (typeof mnemonic === 'string') {
|
||||
+ this.mnemonic = Buffer.from(mnemonic, 'utf8')
|
||||
+ } else if (Array.isArray(mnemonic)) {
|
||||
+ this.mnemonic = Buffer.from(mnemonic)
|
||||
+ } else {
|
||||
+ this.mnemonic = mnemonic
|
||||
+ }
|
||||
+ const seed = bip39.mnemonicToSeed(this.mnemonic)
|
||||
this.hdWallet = hdkey.fromMasterSeed(seed)
|
||||
this.root = this.hdWallet.derivePath(this.hdPath)
|
||||
}
|
37
patches/eth-keyring-controller+6.2.1.patch
Normal file
37
patches/eth-keyring-controller+6.2.1.patch
Normal file
@ -0,0 +1,37 @@
|
||||
diff --git a/node_modules/eth-keyring-controller/index.js b/node_modules/eth-keyring-controller/index.js
|
||||
index 250ab98..38615aa 100644
|
||||
--- a/node_modules/eth-keyring-controller/index.js
|
||||
+++ b/node_modules/eth-keyring-controller/index.js
|
||||
@@ -84,15 +84,20 @@ class KeyringController extends EventEmitter {
|
||||
*
|
||||
* @emits KeyringController#unlock
|
||||
* @param {string} password - The password to encrypt the vault with
|
||||
- * @param {string} seed - The BIP44-compliant seed phrase.
|
||||
+ * @param {string|Array<number>} seedPhrase - The BIP39-compliant seed phrase,
|
||||
+ * either as a string or an array of UTF-8 bytes that represent the string.
|
||||
* @returns {Promise<Object>} A Promise that resolves to the state.
|
||||
*/
|
||||
- createNewVaultAndRestore (password, seed) {
|
||||
+ createNewVaultAndRestore(password, seedPhrase) {
|
||||
+ const seedPhraseAsBuffer = typeof seedPhrase === 'string'
|
||||
+ ? Buffer.from(seedPhrase, 'utf8')
|
||||
+ : Buffer.from(seedPhrase)
|
||||
+
|
||||
if (typeof password !== 'string') {
|
||||
return Promise.reject(new Error('Password must be text.'))
|
||||
}
|
||||
|
||||
- if (!bip39.validateMnemonic(seed)) {
|
||||
+ if (!bip39.validateMnemonic(seedPhraseAsBuffer)) {
|
||||
return Promise.reject(new Error('Seed phrase is invalid.'))
|
||||
}
|
||||
|
||||
@@ -101,7 +106,7 @@ class KeyringController extends EventEmitter {
|
||||
return this.persistAllKeyrings(password)
|
||||
.then(() => {
|
||||
return this.addNewKeyring('HD Key Tree', {
|
||||
- mnemonic: seed,
|
||||
+ mnemonic: seedPhraseAsBuffer,
|
||||
numberOfAccounts: 1,
|
||||
})
|
||||
})
|
@ -82,20 +82,39 @@ export function tryUnlockMetamask(password) {
|
||||
};
|
||||
}
|
||||
|
||||
export function createNewVaultAndRestore(password, seed) {
|
||||
/**
|
||||
* Adds a new account where all data is encrypted using the given password and
|
||||
* where all addresses are generated from a given seed phrase.
|
||||
*
|
||||
* @param {string} password - The password.
|
||||
* @param {string} seedPhrase - The seed phrase.
|
||||
* @returns {Object} The updated state of the keyring controller.
|
||||
*/
|
||||
export function createNewVaultAndRestore(password, seedPhrase) {
|
||||
return (dispatch) => {
|
||||
dispatch(showLoadingIndication());
|
||||
log.debug(`background.createNewVaultAndRestore`);
|
||||
|
||||
// Encode the secret recovery phrase as an array of integers so that it is
|
||||
// serialized as JSON properly.
|
||||
const encodedSeedPhrase = Array.from(
|
||||
Buffer.from(seedPhrase, 'utf8').values(),
|
||||
);
|
||||
|
||||
let vault;
|
||||
return new Promise((resolve, reject) => {
|
||||
background.createNewVaultAndRestore(password, seed, (err, _vault) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
return;
|
||||
}
|
||||
vault = _vault;
|
||||
resolve();
|
||||
});
|
||||
background.createNewVaultAndRestore(
|
||||
password,
|
||||
encodedSeedPhrase,
|
||||
(err, _vault) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
return;
|
||||
}
|
||||
vault = _vault;
|
||||
resolve();
|
||||
},
|
||||
);
|
||||
})
|
||||
.then(() => dispatch(unMarkPasswordForgotten()))
|
||||
.then(() => {
|
||||
@ -117,8 +136,8 @@ export function createNewVaultAndGetSeedPhrase(password) {
|
||||
|
||||
try {
|
||||
await createNewVault(password);
|
||||
const seedWords = await verifySeedPhrase();
|
||||
return seedWords;
|
||||
const seedPhrase = await verifySeedPhrase();
|
||||
return seedPhrase;
|
||||
} catch (error) {
|
||||
dispatch(displayWarning(error.message));
|
||||
throw new Error(error.message);
|
||||
@ -134,9 +153,9 @@ export function unlockAndGetSeedPhrase(password) {
|
||||
|
||||
try {
|
||||
await submitPassword(password);
|
||||
const seedWords = await verifySeedPhrase();
|
||||
const seedPhrase = await verifySeedPhrase();
|
||||
await forceUpdateMetamaskState(dispatch);
|
||||
return seedWords;
|
||||
return seedPhrase;
|
||||
} catch (error) {
|
||||
dispatch(displayWarning(error.message));
|
||||
throw new Error(error.message);
|
||||
@ -185,17 +204,9 @@ export function verifyPassword(password) {
|
||||
});
|
||||
}
|
||||
|
||||
export function verifySeedPhrase() {
|
||||
return new Promise((resolve, reject) => {
|
||||
background.verifySeedPhrase((error, seedWords) => {
|
||||
if (error) {
|
||||
reject(error);
|
||||
return;
|
||||
}
|
||||
|
||||
resolve(seedWords);
|
||||
});
|
||||
});
|
||||
export async function verifySeedPhrase() {
|
||||
const encodedSeedPhrase = await promisifiedBackground.verifySeedPhrase();
|
||||
return Buffer.from(encodedSeedPhrase).toString('utf8');
|
||||
}
|
||||
|
||||
export function requestRevealSeedWords(password) {
|
||||
@ -205,11 +216,11 @@ export function requestRevealSeedWords(password) {
|
||||
|
||||
try {
|
||||
await verifyPassword(password);
|
||||
const seedWords = await verifySeedPhrase();
|
||||
return seedWords;
|
||||
const seedPhrase = await verifySeedPhrase();
|
||||
return seedPhrase;
|
||||
} catch (error) {
|
||||
dispatch(displayWarning(error.message));
|
||||
throw new Error(error.message);
|
||||
throw error;
|
||||
} finally {
|
||||
dispatch(hideLoadingIndication());
|
||||
}
|
||||
|
@ -111,7 +111,9 @@ describe('Actions', () => {
|
||||
|
||||
actions._setBackgroundConnection(background);
|
||||
|
||||
await store.dispatch(actions.createNewVaultAndRestore());
|
||||
await store.dispatch(
|
||||
actions.createNewVaultAndRestore('password', 'test'),
|
||||
);
|
||||
expect(createNewVaultAndRestore.callCount).toStrictEqual(1);
|
||||
});
|
||||
|
||||
@ -134,7 +136,9 @@ describe('Actions', () => {
|
||||
{ type: 'HIDE_LOADING_INDICATION' },
|
||||
];
|
||||
|
||||
await store.dispatch(actions.createNewVaultAndRestore());
|
||||
await store.dispatch(
|
||||
actions.createNewVaultAndRestore('password', 'test'),
|
||||
);
|
||||
|
||||
expect(store.getActions()).toStrictEqual(expectedActions);
|
||||
});
|
||||
@ -155,7 +159,7 @@ describe('Actions', () => {
|
||||
];
|
||||
|
||||
await expect(
|
||||
store.dispatch(actions.createNewVaultAndRestore()),
|
||||
store.dispatch(actions.createNewVaultAndRestore('password', 'test')),
|
||||
).rejects.toThrow('error');
|
||||
|
||||
expect(store.getActions()).toStrictEqual(expectedActions);
|
||||
@ -174,7 +178,7 @@ describe('Actions', () => {
|
||||
cb(),
|
||||
);
|
||||
const verifySeedPhrase = background.verifySeedPhrase.callsFake((cb) =>
|
||||
cb(),
|
||||
cb(null, Array.from(Buffer.from('test').values())),
|
||||
);
|
||||
|
||||
actions._setBackgroundConnection(background);
|
||||
|
Loading…
Reference in New Issue
Block a user