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
6 changes: 3 additions & 3 deletions packages/keyring-eth-ledger-bridge/jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,10 @@ module.exports = merge(baseConfig, {
// An object that configures minimum threshold enforcement for coverage results
coverageThreshold: {
global: {
branches: 91.89,
branches: 92.77,
functions: 97.87,
lines: 97.16,
statements: 97.19,
lines: 97.28,
statements: 97.31,
},
},
});
143 changes: 143 additions & 0 deletions packages/keyring-eth-ledger-bridge/src/ledger-keyring.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { Common, Chain, Hardfork } from '@ethereumjs/common';
import { RLP } from '@ethereumjs/rlp';
import { TransactionFactory } from '@ethereumjs/tx';
import * as ethUtil from '@ethereumjs/util';
import { TransportStatusError } from '@ledgerhq/hw-transport';
import * as sigUtil from '@metamask/eth-sig-util';
import { bytesToHex, Hex, remove0x } from '@metamask/utils';
import EthereumTx from 'ethereumjs-tx';
Expand Down Expand Up @@ -784,6 +785,60 @@ describe('LedgerKeyring', function () {
keyring.signTransaction(fakeAccounts[0], fakeTx),
).rejects.toThrow('Ledger: The transaction signature is not valid');
});

it('throws user rejection error when TransportStatusError with code 27013 is thrown', async function () {
await basicSetupToUnlockOneAccount();
const transportError = {
statusCode: 27013,
message: 'Ledger device: (denied by the user?) (0x6985)',
name: 'TransportStatusError',
};

Object.setPrototypeOf(transportError, TransportStatusError.prototype);
jest
.spyOn(keyring.bridge, 'deviceSignTransaction')
.mockRejectedValue(transportError);

await expect(
keyring.signTransaction(fakeAccounts[0], fakeTx),
).rejects.toThrow('Ledger: User rejected the transaction');
});

it('throws blind signing error when TransportStatusError with code 27264 is thrown', async function () {
await basicSetupToUnlockOneAccount();
const transportError = {
statusCode: 27264,
message: 'Ledger device: Invalid data received (0x6a80)',
name: 'TransportStatusError',
};

Object.setPrototypeOf(transportError, TransportStatusError.prototype);
jest
.spyOn(keyring.bridge, 'deviceSignTransaction')
.mockRejectedValue(transportError);

await expect(
keyring.signTransaction(fakeAccounts[0], fakeTx),
).rejects.toThrow('Ledger: Blind signing must be enabled');
});

it('re-throws TransportStatusError with unknown status code', async function () {
await basicSetupToUnlockOneAccount();
const transportError = {
statusCode: 12345,
message: 'Some other transport error',
name: 'TransportStatusError',
};

Object.setPrototypeOf(transportError, TransportStatusError.prototype);
jest
.spyOn(keyring.bridge, 'deviceSignTransaction')
.mockRejectedValue(transportError);

await expect(
keyring.signTransaction(fakeAccounts[0], fakeTx),
).rejects.toThrow(transportError);
});
});

describe('signPersonalMessage', function () {
Expand Down Expand Up @@ -858,6 +913,40 @@ describe('LedgerKeyring', function () {
'Ledger: The signature doesnt match the right address',
);
});

it('throws user rejection error when TransportStatusError with code 27013 is thrown in signPersonalMessage', async function () {
await basicSetupToUnlockOneAccount();
const transportError = {
statusCode: 27013,
message: 'Ledger device: (denied by the user?) (0x6985)',
name: 'TransportStatusError',
};
Object.setPrototypeOf(transportError, TransportStatusError.prototype);
jest
.spyOn(keyring.bridge, 'deviceSignMessage')
.mockRejectedValue(transportError);

await expect(
keyring.signPersonalMessage(fakeAccounts[0], 'some message'),
).rejects.toThrow('Ledger: User rejected the transaction');
});

it('re-throws TransportStatusError with unknown status code in signPersonalMessage', async function () {
await basicSetupToUnlockOneAccount();
const transportError = {
statusCode: 12345,
message: 'Some other transport error',
name: 'TransportStatusError',
};
Object.setPrototypeOf(transportError, TransportStatusError.prototype);
jest
.spyOn(keyring.bridge, 'deviceSignMessage')
.mockRejectedValue(transportError);

await expect(
keyring.signPersonalMessage(fakeAccounts[0], 'some message'),
).rejects.toThrow(transportError);
});
});

describe('signMessage', function () {
Expand Down Expand Up @@ -1207,6 +1296,60 @@ describe('LedgerKeyring', function () {
'0x72d4e38a0e582e09a620fd38e236fe687a1ec782206b56d576f579c026a7e5b946759735981cd0c3efb02d36df28bb2feedfec3d90e408efc93f45b894946e3200',
);
});

it('throws user rejection error when TransportStatusError with code 27013 is thrown in signTypedData', async function () {
const transportError = {
statusCode: 27013,
message: 'Ledger device: (denied by the user?) (0x6985)',
name: 'TransportStatusError',
};
Object.setPrototypeOf(transportError, TransportStatusError.prototype);
jest
.spyOn(keyring.bridge, 'deviceSignTypedData')
.mockRejectedValue(transportError);

await expect(
keyring.signTypedData(fakeAccounts[15], fixtureData, {
version: sigUtil.SignTypedDataVersion.V4,
}),
).rejects.toThrow('Ledger: User rejected the transaction');
});

it('throws blind signing error when TransportStatusError with code 27264 is thrown in signTypedData', async function () {
const transportError = {
statusCode: 27264,
message: 'Ledger device: Invalid data received (0x6a80)',
name: 'TransportStatusError',
};
Object.setPrototypeOf(transportError, TransportStatusError.prototype);
jest
.spyOn(keyring.bridge, 'deviceSignTypedData')
.mockRejectedValue(transportError);

await expect(
keyring.signTypedData(fakeAccounts[15], fixtureData, {
version: sigUtil.SignTypedDataVersion.V4,
}),
).rejects.toThrow('Ledger: Blind signing must be enabled');
});

it('re-throws TransportStatusError with unknown status code in signTypedData', async function () {
const transportError = {
statusCode: 12345,
message: 'Some other transport error',
name: 'TransportStatusError',
};
Object.setPrototypeOf(transportError, TransportStatusError.prototype);
jest
.spyOn(keyring.bridge, 'deviceSignTypedData')
.mockRejectedValue(transportError);

await expect(
keyring.signTypedData(fakeAccounts[15], fixtureData, {
version: sigUtil.SignTypedDataVersion.V4,
}),
).rejects.toThrow(transportError);
});
});

describe('destroy', function () {
Expand Down
56 changes: 53 additions & 3 deletions packages/keyring-eth-ledger-bridge/src/ledger-keyring.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
type TypedTransaction,
} from '@ethereumjs/tx';
import { publicToAddress } from '@ethereumjs/util';
import { TransportStatusError } from '@ledgerhq/hw-transport';
import type { MessageTypes, TypedMessage } from '@metamask/eth-sig-util';
import {
recoverPersonalSignature,
Expand Down Expand Up @@ -413,7 +414,26 @@ export class LedgerKeyring implements Keyring {
tx: remove0x(rawTxHex),
hdPath,
});
} catch (error) {
} catch (error: unknown) {
// check whether error is TransportError
if (error instanceof TransportStatusError) {
// cast error to TransportError
const transportError: TransportStatusError = error;
if (
transportError.statusCode === 27013 &&
transportError.message.includes('(denied by the user?) (0x6985)')
) {
throw new Error('Ledger: User rejected the transaction');
}

if (
transportError.statusCode === 27264 &&
transportError.message.includes('Invalid data received (0x6a80)')
) {
throw new Error('Ledger: Blind signing must be enabled');
}
}
Comment thread
ccharly marked this conversation as resolved.

throw error instanceof Error
? error
: new Error('Ledger: Unknown error while signing transaction');
Expand Down Expand Up @@ -448,7 +468,20 @@ export class LedgerKeyring implements Keyring {
hdPath,
message: remove0x(message),
});
} catch (error) {
} catch (error: unknown) {
if (error instanceof TransportStatusError) {
const transportError: TransportStatusError = error;
/**
* for user rejected the transaction error
*/
if (
transportError.statusCode === 27013 &&
transportError.message.includes('(denied by the user?) (0x6985)')
) {
throw new Error('Ledger: User rejected the transaction');
}
}
Comment thread
ccharly marked this conversation as resolved.

throw error instanceof Error
? error
: new Error('Ledger: Unknown error while signing message');
Expand Down Expand Up @@ -540,7 +573,24 @@ export class LedgerKeyring implements Keyring {
message,
},
});
} catch (error) {
} catch (error: unknown) {
if (error instanceof TransportStatusError) {
// cast error to TransportError
const transportError: TransportStatusError = error;
if (
transportError.statusCode === 27013 &&
transportError.message.includes('(denied by the user?) (0x6985)')
) {
throw new Error('Ledger: User rejected the transaction');
}

if (
transportError.statusCode === 27264 &&
transportError.message.includes('Invalid data received (0x6a80)')
) {
throw new Error('Ledger: Blind signing must be enabled');
}
}
Comment thread
ccharly marked this conversation as resolved.
throw error instanceof Error
? error
: new Error('Ledger: Unknown error while signing message');
Expand Down
Loading