From 0ecc222ab34fc23479dea4746d4c6e4e6f04a991 Mon Sep 17 00:00:00 2001 From: refi93 Date: Thu, 27 Jun 2024 13:30:39 +0200 Subject: [PATCH 1/3] feat: support custom app secret in the `setup()` call --- src/api/setup.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/api/setup.ts b/src/api/setup.ts index afe85f0b..40e3e5ca 100644 --- a/src/api/setup.ts +++ b/src/api/setup.ts @@ -1,6 +1,6 @@ import { Utils } from '..'; import { Client } from '../client'; -import { setSaveClient, setLoadClient, saveClient, loadClient } from './state'; +import { loadClient, saveClient, setLoadClient, setSaveClient } from './state'; import { buildLoadClientFn, buildSaveClientFn, queue } from './utilities'; /** @@ -15,6 +15,7 @@ type SetupParameters = { deviceId: string; password: string; name: string; + appSecret?: Buffer; getStoredClient: () => string; setStoredClient: (clientData: string | null) => void; }; @@ -28,6 +29,7 @@ type SetupParameters = { * @param {string} SetupParameters.deviceId - the device id of the client * @param {string} SetupParameters.password - the password of the client * @param {string} SetupParameters.name - the name of the client + * @param {Buffer} SetupParameters.appSecret - if set, this app secret will be used instead of the default one * @param {Function} SetupParameters.getStoredClient - a function that returns the stored client data * @param {Function} SetupParameters.setStoredClient - a function that stores the client data * @returns {Promise} - a promise that resolves to a boolean that indicates whether the Client is paired to the application to which it's attempting to connect @@ -37,6 +39,7 @@ export const setup = async ({ deviceId, password, name, + appSecret, getStoredClient, setStoredClient, }: SetupParameters): Promise => { @@ -47,7 +50,8 @@ export const setup = async ({ setLoadClient(buildLoadClientFn(getStoredClient)); if (deviceId && password && name) { - const privKey = Utils.generateAppSecret(deviceId, password, name); + const privKey = + appSecret ?? Utils.generateAppSecret(deviceId, password, name); const client = new Client({ deviceId, privKey, name }); return client.connect(deviceId).then((isPaired) => { saveClient(client.getStateData()); From 2360f22e1b3bbf22dcd7b9028aff9f7e28728ee7 Mon Sep 17 00:00:00 2001 From: refi93 Date: Tue, 23 Jul 2024 09:59:17 +0200 Subject: [PATCH 2/3] fix: patch bignumber to force encoding by borc lib --- src/ethereum.ts | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/src/ethereum.ts b/src/ethereum.ts index d76c00a5..ab5acaae 100644 --- a/src/ethereum.ts +++ b/src/ethereum.ts @@ -2,9 +2,9 @@ // does not have browser (or, by proxy, React-Native) support. import { Chain, Common, Hardfork } from '@ethereumjs/common'; import { TransactionFactory } from '@ethereumjs/tx'; +import { SignTypedDataVersion, TypedDataUtils } from '@metamask/eth-sig-util'; import BN from 'bignumber.js'; import cbor from 'borc'; -import { SignTypedDataVersion, TypedDataUtils } from '@metamask/eth-sig-util'; import { keccak256 } from 'js-sha3'; import { encode as rlpEncode } from 'rlp'; import secp256k1 from 'secp256k1'; @@ -853,6 +853,18 @@ function parseEIP712Msg(msg, typeName, types, forJSParser = false) { return msg; } +class PatchedBN extends BN { + // compatibility hack with borc lib which may internally use a different + // version of the lib which could then cause internal check for type to + // fail, ultimately resulting in incorrect serialization of bignumber instances + // passed from outside + // this line in borc (instanceof instead of `isBigNumber()` check) seems to be the source of the issue: + // https://github.com/dignifiedquire/borc/blob/1bd231963344d8ce2dfb9d9bcf04ea29ff4875de/src/encoder.js#L329 + encodeCBOR(gen) { + return gen._pushBigNumber(gen, this); + } +} + function parseEIP712Item(data, type, forJSParser = false) { if (type === 'bytes') { // Variable sized bytes need to be buffer type @@ -900,7 +912,7 @@ function parseEIP712Item(data, type, forJSParser = false) { // TODO: Find another cbor lib that is compataible with the firmware's lib in a browser // context. This is surprisingly difficult - I tried several libs and only cbor/borc have // worked (borc is a supposedly "browser compatible" version of cbor) - data = new BN(data); + data = new PatchedBN(data); } else if ( ethMsgProtocol.TYPED_DATA.typeCodes[type] && (type.indexOf('uint') > -1 || type.indexOf('int') > -1) @@ -918,8 +930,9 @@ function parseEIP712Item(data, type, forJSParser = false) { // For EIP712 encoding in this module we need strings to represent the numbers data = `0x${b.toString('hex')}`; } else { - // Load into bignumber.js used by cbor lib - data = new BN(b.toString('hex'), 16); + // Load into patched bignumber.js to bypass potential mismatch with bignumber version used + // by borc lib internally + data = new PatchedBN(b.toString('hex'), 16); } } else if (type === 'bool') { // Booleans need to be cast to a u8 From 4baf1dad4a40aff695cc9c0fba56889577f5dffa Mon Sep 17 00:00:00 2001 From: refi93 Date: Tue, 23 Jul 2024 14:45:40 +0200 Subject: [PATCH 3/3] fix: fix ethereum signTypedData result not matching Metamask --- src/ethereum.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ethereum.ts b/src/ethereum.ts index ab5acaae..d36ba0cc 100644 --- a/src/ethereum.ts +++ b/src/ethereum.ts @@ -76,7 +76,7 @@ const validateEthereumMsgResponse = function (res, req) { const digest = prehash ? prehash : encoded; const chainId = parseInt(input.payload.domain.chainId, 16); // Get recovery param with a `v` value of [27,28] by setting `useEIP155=false` - return addRecoveryParam(digest, sig, signer, { chainId }); + return addRecoveryParam(digest, sig, signer, { chainId, useEIP155: false }); } else { throw new Error('Unsupported protocol'); }