Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

metamask patch init #1

Merged
merged 8 commits into from
Sep 7, 2022
Merged

metamask patch init #1

merged 8 commits into from
Sep 7, 2022

Conversation

adonesky1
Copy link

@adonesky1 adonesky1 commented Aug 23, 2022

Metamask-extension currently uses a fork of bitcoinjs/bip39 (its used in eth-hd-keyring which is used by eth-keyring-controller which is used in the extension.

While seeking to remove extraneous wordlists from the extension bundle as part of the MV3 effort, I recalled that we had discussed switching our BIP39 library from bitcoinjs/bip39 to @scure/bip39.

This PR applies the required patches to this new MetaMask owned fork of @scure/bip39 to preserve the security requirement that we store and pass mnemonics in a format other than plain-text/string. As part of the transition to this implementation, mnemonics will be stored/passed as Uint8Arrays instead of Buffers. I have POC/tested this new interface in a string of PRs along the dependency tree back to the extension: this pr -> eth-hd-keyring -> eth-keyring-controller -> extension.

@paulmillr
Copy link

Seems fine. I am actually considering adding this feature upstream. Of course, without Buffers — huge aficionado of not using them because of security, lack of browser support, the fact uint8arrays are supported everywhere, etc.

@paulmillr
Copy link

paulmillr commented Aug 24, 2022

Before, mnemonic was string. Currently, you're adding three new types: Uint8Array, Buffer, number[].

  • Uint8Array represents array indexes — all good here, very efficient and effective
  • Buffer, number[] represent string mapped over with new TextEncoder().encode(), basically converted to byte array. I would consider this as a bad API — why not just use Uint8Array and array indexes? Is your main concern a backwards compatibility? How about adding a wrapper function to metamask itself, that would convert from old Buffer mnemonics to new indexes and keeping the API lightweight?
  • Buffer is often used as a substitution for Uint8Array — even more, Buffer instanceof Uint8Array is true. Which means, someone may think by mistake they can interchange them, while the logic is radically different
  • number[] is another alternative for Buffer — not sure i'm seeing the point here — would rather just keep Buffer / Uint8Array. It would made sense to have this api to represent indexes - but not encoded strings.

@paulmillr
Copy link

paulmillr commented Aug 24, 2022

Clarification: your current approach takes Buffer.from(str).length 73+ bytes to store some 12-word mnemonic; that's 147+ bytes for 24-word.

If you switch to the new way, which is coded in this pull request as a first if condition, it could instead consume just a 24-byte Uint8Array / 12-element number[] / 48-character hex string / 32-character base64 string

@adonesky1
Copy link
Author

adonesky1 commented Aug 24, 2022

Is your main concern a backwards compatibility

Yes this was the primary concern. But I agree keeping the API lightweight makes sense.

Could you provide some guidance on the cleanest way in your view to make this conversion, and then I can apply on the consumer side.

Think I've got it ☝️ 👍

@adonesky1
Copy link
Author

@paulmillr is there some trick to the test suite? I can't seem to get the err in assert.js to throw, so tests are passing no matter what I pass to deepStrictEqual?

@adonesky1 adonesky1 requested review from paulmillr, kumavis and Gudahtt and removed request for paulmillr August 25, 2022 17:22
@adonesky1 adonesky1 marked this pull request as ready for review August 25, 2022 18:00
entropy = getCoder(wordlist).decode(
Array.from(new Uint16Array(mnemonic.buffer)).map((i) => wordlist[i])
);
}
assertEntropy(entropy);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking much better now!

Thinking about it, maybe we should just number[] a.k.a. array of indexes instead of Uint8Array? It would become:

entropy = getCoder(wordlist).decode(mnemonic.map((i) => wordlist[i]));

Having number[] is even better: it clearly says "I am waiting for array of numbers". If we keep Uint8Array, that would mean "we are waiting for some buffer, which is not really a buffer, but an array of numbers".

Copy link
Author

@adonesky1 adonesky1 Sep 7, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think for now we will keep Uint8Array for uniformity and flexibility and consider migrating all to number[] at some point.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Understood, I will probably add the number[] to the main library and you won't need to maintain the fork when you'll decide to go number[] way.

src/index.ts Outdated Show resolved Hide resolved
@@ -96,16 +104,21 @@ export function mnemonicToEntropy(mnemonic: string, wordlist: string[]): Uint8Ar
* entropyToMnemonic(ent, wordlist);
* // 'legal winner thank year wave sausage worth useful legal winner thank yellow'
*/
export function entropyToMnemonic(entropy: Uint8Array, wordlist: string[]): string {
export function entropyToMnemonic(entropy: Uint8Array, wordlist: string[]): Uint8Array {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe keep the method as-is, and create a new one:

export function entropyToMnemonicIndexes(entropy: Uint8Array, wordlist: string[]): number[] {
  assertEntropy(entropy);
  const words = getCoder(wordlist).encode(entropy);
  return words.map((word) => wordlist.indexOf(word));
}

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure we can do this, though we won't be using the old one with this fork...

@paulmillr
Copy link

Also "indexes" and "indices" both seem to be correct.

@adonesky1
Copy link
Author

adonesky1 commented Sep 6, 2022

is there some trick to the test suite? I can't seem to get the err in assert.js to throw, so tests are passing no matter what I pass to deepStrictEqual?

Any ideas on this @paulmillr ?

@paulmillr
Copy link

Not sure what's the problem here. Assuming you're running built-in nodejs assertion library?

@adonesky1
Copy link
Author

Assuming you're running built-in nodejs assertion library?

yes indeed

@adonesky1
Copy link
Author

deepStrictEqual is async... so it always needs to be awaited... not sure why its not on main in the base repo? @paulmillr

@paulmillr
Copy link

Where? It's sync in docs

https://nodejs.org/api/assert.html

@adonesky1
Copy link
Author

no I meant your implementation of deepStrictEqual is async

@paulmillr
Copy link

paulmillr commented Sep 7, 2022

Sorry - not sure how i've missed async function. Removing async from it works just fine. I will do this now.

Still suggesting to change Uint8Array to number[] and passing 12-element array instead of 20+ element Uint8Array. What do you think?

update: saw your comment, all cool

@adonesky1
Copy link
Author

@paulmillr thanks for all your help with this 🤝

@adonesky1 adonesky1 merged commit 47f348a into main Sep 7, 2022
@kumavis kumavis deleted the metamask-patch-cleanup branch September 7, 2022 21:13
@adonesky1 adonesky1 mentioned this pull request Sep 12, 2022
Closed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
3 participants