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

feat: support custom app secret in the setup() call + EVM signTypedData fixes #563

Closed
wants to merge 3 commits into from
Closed
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
8 changes: 6 additions & 2 deletions src/api/setup.ts
Original file line number Diff line number Diff line change
@@ -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';

/**
Expand All @@ -15,6 +15,7 @@ type SetupParameters = {
deviceId: string;
password: string;
name: string;
appSecret?: Buffer;
getStoredClient: () => string;
setStoredClient: (clientData: string | null) => void;
};
Expand All @@ -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<boolean>} - a promise that resolves to a boolean that indicates whether the Client is paired to the application to which it's attempting to connect
Expand All @@ -37,6 +39,7 @@ export const setup = async ({
deviceId,
password,
name,
appSecret,
getStoredClient,
setStoredClient,
}: SetupParameters): Promise<boolean> => {
Expand All @@ -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());
Expand Down
23 changes: 18 additions & 5 deletions src/ethereum.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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 });
Copy link
Author

@refi93 refi93 Jul 23, 2024

Choose a reason for hiding this comment

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

as far as I checked on metamask demo app, metamask apparently still returns a non-EIP155 signature of typed data V4 and if I didn't disable EIP155, the signature failed verification in the demo dapp: https://metamask.github.io/test-dapp/

} else {
throw new Error('Unsupported protocol');
}
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand All @@ -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
Expand Down