-
-
Notifications
You must be signed in to change notification settings - Fork 4
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
refactor!: migrate HdKeyring
to typescript
#166
base: main
Are you sure you want to change the base?
Conversation
Updated dependencies detected. Learn more about Socket for GitHub ↗︎
|
HdKeyring
to typescriptHdKeyring
to typescript
opts: HDKeyringAccountSelectionOptions = {}, | ||
): Promise<string> { | ||
const privKey = this.#getPrivateKeyFor(withAccount, opts); | ||
const publicKey = getEncryptionPublicKey(bytesToHex(privKey)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In the original implementation privKey
was passed to getEncryptionPublicKey
directly:
const publicKey = getEncryptionPublicKey(privKey);
Though, the addition of types evidenced that getEncryptionPublicKey
only accept hex strings, hence the conversion with bytesToHex
packages/keyring-eth-hd/src/index.ts
Outdated
/** | ||
* Get the wallet for the specified account. | ||
* | ||
* @param account - The address of the account. | ||
* @returns The wallet for the account as HDKey. | ||
*/ | ||
#getWalletForAccount(account: string): HDKey; | ||
|
||
/** | ||
* Get the wallet for the specified account and app origin. | ||
* | ||
* @param account - The address of the account. | ||
* @param opts - The options for selecting the account. | ||
* @returns A key pair representing the wallet. | ||
*/ | ||
#getWalletForAccount( | ||
accounts: string, | ||
opts: { withAppKeyOrigin: string }, | ||
): { privateKey: Buffer; publicKey: Buffer }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This internal method is overloaded because the objects returned can vary significantly based on whether an app key origin is specified (i.e. a Buffer keypair is returned instead of an entire HDKey instance).
The actual signature of the implementation is left unchanged.
"@metamask/eth-hd-keyring": "4.0.1", | ||
"@ts-bridge/cli": "^0.6.1", | ||
"@types/jest": "^29.5.12", | ||
"deepmerge": "^4.2.2", | ||
"jest": "^29.5.0" | ||
"jest": "^29.5.0", | ||
"old-hd-keyring": "npm:@metamask/eth-hd-keyring@^4.0.1" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This package used for testing backward compatibility was overwriting the current version of @metamask/eth-hd-keyring
, causing TS to apply the same types of the new package. With this line we can import the package with a different name.
The version 4.0.1
was pinned, but yarn lint
was failing and I guess that ^4.0.1
serves the same purpose
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There should be no functional change in these test cases, besides the ones to make TS happy
@metamaskbot publish-preview |
Preview builds have been published. See these instructions (from the Expand for full list of packages and versions.
|
// WARN: verify this cast to Buffer | ||
msgSig.v as unknown as Buffer, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It looks like @metamask/eth-sig-util
only accepts a Buffer here, while msgSig.v
seems to be a bigint
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, does that cause an issue? A bigint is not a Buffer.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The implementation is unchanged and is the same that is in prod already:
accounts/packages/keyring-eth-hd/src/index.js
Line 185 in 3111c56
const rawMsgSig = concatSig(msgSig.v, msgSig.r, msgSig.s); |
concatSig
(which expects a Buffer)
Though now the same code is throwing a type error. I think I left that // WARN
there to come back at this and see if we can apply a conversion without breaking things
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, strange, okay.
@@ -154,7 +159,7 @@ describe('hd-keyring', () => { | |||
numberOfAccounts: 2, | |||
}); | |||
|
|||
const accounts = await keyring.getAccounts(); | |||
const accounts = keyring.getAccounts(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
getAccounts()
was awaited everywhere in the tests, but the function implementation was not marked as async
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we can change the tests here, and then change them back along with fixing the implementation when HDKeyring
will implement the Keyring
type
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks like a good conversion overall, just had some questions on specific lines.
packages/keyring-eth-hd/src/index.ts
Outdated
this.#wallets.push(wallet); | ||
} | ||
const hexWallets = newWallets.map((wallet) => { | ||
return this.#addressfromPublicKey(wallet.publicKey as Uint8Array); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do we have to make this type assertion?
*/ | ||
getAccounts(): string[] { | ||
return this.#wallets.map((wallet) => { | ||
assert(wallet.publicKey, 'Expected public key to be set'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we simply throw an error instead of using assert
? Otherwise this library is Node-only (or else, assert
needs to be polyfilled). Unless it's already Node-only?
assert(wallet.publicKey, 'Expected public key to be set'); | |
if (wallet.publicKey === undefined) { | |
throw new Error('Expected public key to be set'); | |
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, this assert
comes from @metamask/utils
instead of the built-in Node.js' assert
. Looking at its implementation it seems to be compatible with a browser environment as well: https://github.com/MetaMask/utils/blob/90837bad1d85277aa5b8f16fe40beb81e0a1e09d/src/assert.ts#L81
Having this in mind, do you still think we should substitute these with explicit if undefined ?
const wallet = this.#getWalletForAccount(address, { | ||
withAppKeyOrigin: origin, | ||
}); | ||
assert(wallet.publicKey, 'Expected public key to be set'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Similar as above?
assert(wallet.publicKey, 'Expected public key to be set'); | |
if (wallet.publicKey === undefined) { | |
throw new Error('Expected public key to be set'); | |
} |
? this.#getWalletForAccount(address, opts) | ||
: this.#getWalletForAccount(address); | ||
const { privateKey } = wallet; | ||
assert( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Another case where we could possibly throw an error manually instead of using assert
? (You get the idea)
const msgSig = ecsign(Buffer.from(message, 'hex'), Buffer.from(privKey)); | ||
const rawMsgSig = concatSig( | ||
// WARN: verify this cast to Buffer | ||
msgSig.v as unknown as Buffer, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Curious, why do we need to make a type assertion here? I think you answered this question above
|
||
const privateKey = this.#getPrivateKeyFor(withAccount, opts); | ||
return signTypedData({ | ||
privateKey: Buffer.from(privateKey), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It looks like we are converting privateKey
to a Buffer when we weren't before. Is this a breaking change or was this supposed to be this way all along?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since #getPrivateKeyFor
returns a Uint8Array or a Buffer depending on whether we are passing { withAppKeyOrigin: string }
as opts
, signTypedData
could receive something that is not a Buffer.
This is to say that it was supposed to be this way all along. Using Buffer.from(privateKey)
should convert the Uint8Array to a Buffer or just return another Buffer with the same content as the original privateKey
mnemonic: Buffer | JsCastedBuffer | string | Uint8Array | number[], | ||
): Uint8Array { | ||
let mnemonicData: unknown = mnemonic; | ||
// when encrypted/decrypted, buffers get cast into js object with a property type set to buffer |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This explanation sounds a bit vague to me. What does it mean to cast a buffer into a JS object? Are we trying to serialize the buffer? I guess we could leave this comment here for now but I'm curious if there is a more descriptive name for this function.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The comment is left unchanged from the original repo, though I think that it is referring to the output of buffer.toJSON()
which returns an object with this shape:
{
type: 'Buffer',
data: [
71, 101, 101, 107,
115, 102, 111, 114,
71, 101, 101, 107,
115
]
}
Perhaps we can change it to be something like this:
// when encrypted/decrypted, buffers get cast into js object with a property type set to buffer | |
// When using Buffer.toJSON(), the Buffer is serialized into an object | |
// with the structure `{ type: 'Buffer', data: [...] }` |
packages/keyring-eth-hd/src/index.ts
Outdated
* @param publicKey - The public key of the account. | ||
* @returns The address of the account. | ||
*/ | ||
#addressfromPublicKey(publicKey: Uint8Array): string { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does it make sense for this return type to be Hex
? Or does that screw things up elsewhere?
(I think we have a function in @metamask/utils
that we can use instead of bufferToHex
which already returns a Hex
, so if we want to / plan on using that later, that's fine, too.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I changed all types related to addresses from string
to Hex
. I guess you were referring to add0x
which is now used to normalize addresses when needed (added HdKeyring.#normalizeAddress
): 4d86d8e
The following extension PR is using a package preview from this PR, which appears to be working fine: MetaMask/metamask-extension#29961 |
The
@metamask/eth-hd-keyring
is being migrated to Typescript.The changes applied can be seen as diff in this commit: bc89c46
This package migration can be tested on this extension PR: MetaMask/metamask-extension#29961
There is no consumer-facing change, besides this one:
HdKeyring
HdKeyring
retain their existing signatures, but now have types.Fixes: #92