Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions .yarn/patches/@ethereumjs-util-npm-9.1.0-7e85509408.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
diff --git a/dist/cjs/account.js b/dist/cjs/account.js
index 9c7b96d50a1e1e9a08463e0be74f1462576b8b53..04f390b50c11a10b20971bccb46d212978c2ac89 100644
--- a/dist/cjs/account.js
+++ b/dist/cjs/account.js
@@ -476,7 +476,7 @@ const pubToAddress = function (pubKey, sanitize = false) {
// Only take the lower 160bits of the hash
return (0, keccak_js_1.keccak256)(pubKey).subarray(-20);
};
-exports.pubToAddress = pubToAddress;
+exports.pubToAddress = require('@metamask/native-utils').pubToAddress;
exports.publicToAddress = exports.pubToAddress;
/**
* Returns the ethereum public key of a given private key.
21 changes: 21 additions & 0 deletions .yarn/patches/@metamask-key-tree-npm-10.1.1-0bfab435ac.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
diff --git a/dist/curves/ed25519.cjs b/dist/curves/ed25519.cjs
index 3f6b0951c046dbda89f18edb7f17e6d23b839fc5..d2aee95598d942c0219128a77f6f83abdf206b80 100644
--- a/dist/curves/ed25519.cjs
+++ b/dist/curves/ed25519.cjs
@@ -14,6 +14,7 @@ const isValidPrivateKey = (_privateKey) => true;
exports.isValidPrivateKey = isValidPrivateKey;
exports.deriveUnhardenedKeys = false;
exports.publicKeyLength = 33;
+const nativeUtils = require('@metamask/native-utils')
const getGetPublicKey = () => {
let hasSetWindowSize = false;
const getPublicKey = (privateKey, _compressed) => {
@@ -21,7 +22,7 @@ const getGetPublicKey = () => {
ed25519_1.ed25519.ExtendedPoint.BASE._setWindowSize(4);
hasSetWindowSize = true;
}
- const publicKey = ed25519_1.ed25519.getPublicKey(privateKey);
+ const publicKey = nativeUtils.getPublicKeyEd25519(privateKey);
return (0, utils_1.concatBytes)([new Uint8Array([0]), publicKey]);
};
return getPublicKey;
42 changes: 42 additions & 0 deletions app/__mocks__/@metamask/native-utils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/* eslint-disable import/no-extraneous-dependencies */
/* eslint-disable import/no-commonjs */
/**
* Mock for @metamask/native-utils
*
* This module uses react-native-nitro-modules which requires native code.
* In Jest tests, we use the original JavaScript implementations from @noble packages.
*/

const { secp256k1 } = require('@noble/curves/secp256k1');
const { ed25519 } = require('@noble/curves/ed25519');
const { keccak_256 } = require('@noble/hashes/sha3');
const { hmac } = require('@noble/hashes/hmac');
const { sha512 } = require('@noble/hashes/sha2');

export const getPublicKey = secp256k1.getPublicKey;
export const keccak256 = keccak_256;
export const hmacSha512 = (key, data) => hmac(sha512, key, data);
export const getPublicKeyEd25519 = ed25519.getPublicKey;
export const multiply = (a, b) => a * b;

/**
* Reimplemented from @ethereumjs/util.
*
* We cannot import pubToAddress from @ethereumjs/util directly because it's
* patched to use @metamask/native-utils, which would cause infinite recursion:
* 1. Our mock's pubToAddress is called
* 2. It requires @ethereumjs/util
* 3. @ethereumjs/util's exports.pubToAddress = require('@metamask/native-utils').pubToAddress
* 4. That returns our mock's pubToAddress
* 5. We call ourselves → stack overflow
*/
export const pubToAddress = (pubKey, sanitize = false) => {
let key = pubKey;
if (sanitize && pubKey.length !== 64) {
key = secp256k1.ProjectivePoint.fromHex(pubKey).toRawBytes(false).slice(1);
}
if (key.length !== 64) {
throw new Error('Expected pubKey to be of length 64');
}
return keccak_256(key).subarray(-20);
};
4 changes: 2 additions & 2 deletions app/core/Encryptor/lib/quick-crypto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ class QuickCryptoEncryptionLibrary implements EncryptionLibrary {
salt: string,
opts: KeyDerivationOptions,
): Promise<string> => {
const passBuffer = Buffer.from(password, 'utf-8');
const passBuffer = new TextEncoder().encode(password);

const baseKey = await Crypto.subtle.importKey(
'raw',
Expand Down Expand Up @@ -77,7 +77,7 @@ class QuickCryptoEncryptionLibrary implements EncryptionLibrary {
* @returns A promise that resolves to the encrypted data as a base64 string.
*/
encrypt = async (data: string, key: string, iv: string): Promise<string> => {
const dataBuffer = Buffer.from(data, 'utf-8');
const dataBuffer = new TextEncoder().encode(data);
const ivBuffer = Buffer.from(iv, 'hex');
const cryptoKey = await this.importKey(key);

Expand Down
33 changes: 21 additions & 12 deletions app/selectors/accountsController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,20 +54,29 @@ export const selectInternalAccounts = createDeepEqualSelector(
selectAccountsControllerState,
selectFlattenedKeyringAccounts,
(accountControllerState, orderedKeyringAccounts): InternalAccount[] => {
const keyringAccountsMap = new Map(
orderedKeyringAccounts.map((account, index) => [
toFormattedAddress(account),
index,
]),
);
const sortedAccounts = Object.values(
// Build index map from formatted keyring addresses: O(n) calls to toFormattedAddress
const keyringIndexMap = new Map<string, number>();
for (let i = 0; i < orderedKeyringAccounts.length; i++) {
keyringIndexMap.set(toFormattedAddress(orderedKeyringAccounts[i]), i);
}

const accounts = Object.values(
accountControllerState.internalAccounts.accounts,
).sort(
(a, b) =>
(keyringAccountsMap.get(toFormattedAddress(a.address)) || 0) -
(keyringAccountsMap.get(toFormattedAddress(b.address)) || 0),
);
return sortedAccounts;

// Pre-compute sort index for each account: O(m) calls to toFormattedAddress
const sortIndices = new Map<InternalAccount, number>();
for (const account of accounts) {
sortIndices.set(
account,
keyringIndexMap.get(toFormattedAddress(account.address)) ?? 0,
);
}

// Sort using pre-computed indices: O(m log m) but NO toFormattedAddress calls
return [...accounts].sort(
(a, b) => (sortIndices.get(a) ?? 0) - (sortIndices.get(b) ?? 0),
);
},
);

Expand Down
4 changes: 2 additions & 2 deletions ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -446,7 +446,7 @@ PODS:
- nanopb/encode (= 2.30910.0)
- nanopb/decode (2.30910.0)
- nanopb/encode (2.30910.0)
- NativeUtils (0.5.0):
- NativeUtils (0.8.0):
- DoubleConversion
- glog
- hermes-engine
Expand Down Expand Up @@ -3474,7 +3474,7 @@ SPEC CHECKSUMS:
lottie-react-native: 7f3fc3f396b1d6c7b1454b77596bd2ad3151871e
MultiplatformBleAdapter: b1fddd0d499b96b607e00f0faa8e60648343dc1d
nanopb: 438bc412db1928dac798aa6fd75726007be04262
NativeUtils: ff6b807548ac292267c8bd50b6f1dfb4c9f056d3
NativeUtils: e1d5591114bd87ba0b91348477f77b029cd361b8
NitroModules: 54cf4604a7e458d788aeecb3ba1ff7db43ed17f2
OpenSSL-Universal: 6082b0bf950e5636fe0d78def171184e2b3899c2
Permission-BluetoothPeripheral: 34ab829f159c6cf400c57bac05f5ba1b0af7a86e
Expand Down
4 changes: 3 additions & 1 deletion jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ const config = {
setupFilesAfterEnv: ['<rootDir>/app/util/test/testSetup.js'],
testEnvironment: 'jest-environment-node',
transformIgnorePatterns: [
'node_modules/(?!((@metamask/)?(@react-native|react-native|redux-persist-filesystem|@react-navigation|@react-native-community|@react-native-masked-view|react-navigation|react-navigation-redux-helpers|@sentry|d3-color|d3-shape|d3-path|d3-scale|d3-array|d3-time|d3-format|d3-interpolate|d3-selection|d3-axis|d3-transition|internmap|react-native-wagmi-charts|@notifee|expo-file-system|expo-modules-core|expo(nent)?|@expo(nent)?/.*)|@noble/.*|@nktkas/hyperliquid|@metamask/design-system-twrnc-preset|@metamask/design-system-react-native|@tommasini/react-native-scrollable-tab-view))',
'node_modules/(?!((@metamask/)?(@react-native|react-native|redux-persist-filesystem|@react-navigation|@react-native-community|@react-native-masked-view|react-navigation|react-navigation-redux-helpers|@sentry|d3-color|d3-shape|d3-path|d3-scale|d3-array|d3-time|d3-format|d3-interpolate|d3-selection|d3-axis|d3-transition|internmap|react-native-wagmi-charts|react-native-nitro-modules|@notifee|expo-file-system|expo-modules-core|expo(nent)?|@expo(nent)?/.*)|@noble/.*|@nktkas/hyperliquid|@metamask/design-system-twrnc-preset|@metamask/design-system-react-native|@metamask/native-utils|@tommasini/react-native-scrollable-tab-view))',
],
transform: {
'^.+\\.[jt]sx?$': ['babel-jest', { configFile: './babel.config.tests.js' }],
Expand Down Expand Up @@ -64,6 +64,8 @@ const config = {
'\\webview/index.html': '<rootDir>/app/__mocks__/htmlMock.ts',
'^@expo/vector-icons@expo/vector-icons$': 'react-native-vector-icons',
'^@expo/vector-icons/(.*)': 'react-native-vector-icons/$1',
'^@metamask/native-utils$':
'<rootDir>/app/__mocks__/@metamask/native-utils.js',
'^@nktkas/hyperliquid(/.*)?$': '<rootDir>/app/__mocks__/hyperliquidMock.js',
'^expo-auth-session(/.*)?$': '<rootDir>/app/__mocks__/expo-auth-session.js',
'^expo-apple-authentication(/.*)?$':
Expand Down
12 changes: 10 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -168,8 +168,14 @@
"@expo/fingerprint": "^0.15.0",
"appwright@^0.1.45": "patch:appwright@npm%3A0.1.45#./.yarn/patches/appwright-npm-0.1.45-f282bc1c1b.patch",
"@scure/bip32": "1.7.0",
"js-sha3": "0.9.3",
"@metamask/snaps-sdk": "^10.0.0",
"react-native@0.76.9": "patch:react-native@npm%3A0.76.9#./.yarn/patches/react-native-npm-0.76.9-1c25352097.patch",
"@ethereumjs/util@npm:^9.0.3": "patch:@ethereumjs/util@npm%3A9.1.0#~/.yarn/patches/@ethereumjs-util-npm-9.1.0-7e85509408.patch",
"@ethereumjs/util@npm:^9.1.0": "patch:@ethereumjs/util@npm%3A9.1.0#~/.yarn/patches/@ethereumjs-util-npm-9.1.0-7e85509408.patch",
"@ethereumjs/util@npm:^9.0.2": "patch:@ethereumjs/util@npm%3A9.1.0#~/.yarn/patches/@ethereumjs-util-npm-9.1.0-7e85509408.patch",
"@metamask/key-tree@npm:^10.1.1": "patch:@metamask/key-tree@npm%3A10.1.1#~/.yarn/patches/@metamask-key-tree-npm-10.1.1-0bfab435ac.patch",
"@metamask/key-tree@npm:^10.0.2": "patch:@metamask/key-tree@npm%3A10.1.1#~/.yarn/patches/@metamask-key-tree-npm-10.1.1-0bfab435ac.patch",
"@metamask/transaction-controller@npm:^62.5.0": "patch:@metamask/transaction-controller@npm%3A62.5.0#~/.yarn/patches/@metamask-transaction-controller-npm-61.0.0-cccac388c7.patch"
},
"dependencies": {
Expand Down Expand Up @@ -226,7 +232,7 @@
"@metamask/gator-permissions-controller": "^0.3.0",
"@metamask/json-rpc-engine": "^10.2.0",
"@metamask/json-rpc-middleware-stream": "^8.0.7",
"@metamask/key-tree": "^10.1.1",
"@metamask/key-tree": "patch:@metamask/key-tree@npm%3A10.1.1#~/.yarn/patches/@metamask-key-tree-npm-10.1.1-0bfab435ac.patch",
"@metamask/keyring-api": "^21.2.0",
"@metamask/keyring-controller": "^24.0.0",
"@metamask/keyring-internal-api": "^9.1.0",
Expand All @@ -242,7 +248,7 @@
"@metamask/multichain-api-middleware": "1.2.4",
"@metamask/multichain-network-controller": "^2.0.0",
"@metamask/multichain-transactions-controller": "^6.0.0",
"@metamask/native-utils": "^0.5.0",
"@metamask/native-utils": "^0.8.0",
"@metamask/network-controller": "^27.0.0",
"@metamask/network-enablement-controller": "patch:@metamask/network-enablement-controller@npm%3A3.1.0#~/.yarn/patches/@metamask-network-enablement-controller-npm-3.1.0-1c0cfefdc3.patch",
"@metamask/notification-services-controller": "^20.0.0",
Expand Down Expand Up @@ -288,6 +294,7 @@
"@ngraveio/bc-ur": "^1.1.6",
"@nktkas/hyperliquid": "^0.27.1",
"@noble/curves": "1.9.6",
"@noble/hashes": "1.8.0",
"@notifee/react-native": "^9.0.0",
"@react-native-async-storage/async-storage": "^1.23.1",
"@react-native-clipboard/clipboard": "^1.16.1",
Expand Down Expand Up @@ -374,6 +381,7 @@
"human-standard-token-abi": "^2.0.0",
"humanize-duration": "^3.27.2",
"is-url": "^1.2.4",
"js-sha3": "0.9.3",
"lodash": "^4.17.21",
"lottie-react-native": "6.7.2",
"luxon": "^3.5.0",
Expand Down
8 changes: 1 addition & 7 deletions shim.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,7 @@ import {
} from './app/util/test/utils.js';
import { defaultMockPort } from './e2e/api-mocking/mock-config/mockUrlCollection.json';

import { getPublicKey } from '@metamask/native-utils';

// polyfill getPublicKey with much faster C++ implementation
// IMPORTANT: This patching works only if @noble/curves version in root package.json is same as @noble/curves version in package.json of @scure/bip32.
// eslint-disable-next-line import/no-commonjs, import/no-extraneous-dependencies
const secp256k1_1 = require('@noble/curves/secp256k1');
secp256k1_1.secp256k1.getPublicKey = getPublicKey;
import './shimPerf';

// Needed to polyfill random number generation
import 'react-native-get-random-values';
Expand Down
69 changes: 69 additions & 0 deletions shimPerf.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
/* eslint-disable import/no-extraneous-dependencies */
/* eslint-disable import/no-commonjs */
/* eslint-disable import/no-nodejs-modules */
import { Buffer } from '@craftzdog/react-native-buffer';

import { getPublicKey, hmacSha512, keccak256 } from '@metamask/native-utils';

// Monkey patch getPublicKey from @noble/curves with much faster C++ implementation
// IMPORTANT: This patching works only if @noble/curves version in root package.json is same as @noble/curves version in package.json of @scure/bip32.
const secp256k1_1 = require('@noble/curves/secp256k1');
secp256k1_1.secp256k1.getPublicKey = getPublicKey;

// Monkey patch hmacSha512 from @noble/hashes
const nobleHashesHmac = require('@noble/hashes/hmac');
const nobleHashesSha2 = require('@noble/hashes/sha2');
const originalHmac = nobleHashesHmac.hmac;
nobleHashesHmac.hmac = (hash, key, message) => {
if (hash === nobleHashesSha2.sha512) {
try {
return hmacSha512(key, message);
} catch (error) {
console.error(
'Error in @metamask/native-utils.hmacSha512, falling back to original implementation',
error,
);
}
}
return originalHmac(hash, key, message);
};

// Monkey patch keccak256 from @noble/hashes
const nobleHashesSha3 = require('@noble/hashes/sha3');
const originalNobleHashesSha3Keccak256 = nobleHashesSha3.keccak_256;
const patchedNobleHashesSha3Keccak256 = (value) => {
try {
return keccak256(value);
} catch (error) {
console.error(
'Error in @metamask/native-utils.keccak256, falling back to original implementation',
error,
);
}
return originalNobleHashesSha3Keccak256(value);
};
// We need to use Object.assign to ensure added properties are not overridden (e.g. keccak_256.create())
Object.assign(
patchedNobleHashesSha3Keccak256,
originalNobleHashesSha3Keccak256,
);
nobleHashesSha3.keccak_256 = patchedNobleHashesSha3Keccak256;

// Monkey patch keccak256 from js-sha3
const jsSha3 = require('js-sha3');
const originalJsSha3Keccak256 = jsSha3.keccak_256;
const patchedJsSha3Keccak256 = (value) => {
try {
// js-sha3 returns hex string not Uint8Array
return Buffer.from(keccak256(value)).toString('hex');
} catch (error) {
console.error(
'Error in @metamask/native-utils.keccak256, falling back to original js-sha3 implementation',
error,
);
}
return originalJsSha3Keccak256(value);
};
// We need to use Object.assign to ensure added properties are not overridden (e.g. keccak256.create())
Object.assign(patchedJsSha3Keccak256, originalJsSha3Keccak256);
jsSha3.keccak_256 = patchedJsSha3Keccak256;
Loading
Loading