Skip to content

Commit

Permalink
feat: ledger thorchain support
Browse files Browse the repository at this point in the history
  • Loading branch information
gomesalexandre committed Oct 9, 2023
1 parent 26df603 commit 7d28d0b
Show file tree
Hide file tree
Showing 10 changed files with 563 additions and 2 deletions.
7 changes: 7 additions & 0 deletions packages/hdwallet-ledger-webusb/src/transport.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,13 @@ export async function translateCoinAndMethod<T extends LedgerTransportCoinType,
method: U
): Promise<LedgerTransportMethod<T, U>> {
switch (coin) {
case "Rune": {
const thor = new ledger.THORChainApp({ transport });
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore TODO(gomes): fixme
const methodInstance = thor[method as LedgerTransportMethodName<"Rune">].bind(thor);
return methodInstance as LedgerTransportMethod<T, U>;
}
case "Btc": {
const btc = new Btc({ transport });
const methodInstance = btc[method as LedgerTransportMethodName<"Btc">].bind(btc);
Expand Down
4 changes: 3 additions & 1 deletion packages/hdwallet-ledger/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,9 @@
"bs58check": "2.1.2",
"ethereumjs-tx": "1.3.7",
"ethereumjs-util": "^6.1.0",
"lodash": "^4.17.21"
"lodash": "^4.17.21",
"ripemd160": "^2.0.2",
"@types/ripemd160": "^2.0.1"
},
"devDependencies": {
"@ledgerhq/hw-app-btc": "^10.0.8",
Expand Down
1 change: 1 addition & 0 deletions packages/hdwallet-ledger/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export * from "./bitcoin";
export * from "./ethereum";
export * from "./thorchain";
export * from "./ledger";
export * from "./transport";
export * from "./utils";
6 changes: 6 additions & 0 deletions packages/hdwallet-ledger/src/ledger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import _ from "lodash";

import * as btc from "./bitcoin";
import * as eth from "./ethereum";
import * as thorchain from "./thorchain";
import { LedgerTransport } from "./transport";
import { coinToLedgerAppName, handleError } from "./utils";

Expand Down Expand Up @@ -302,6 +303,8 @@ export class LedgerHDWallet implements core.HDWallet, core.BTCWallet, core.ETHWa
readonly _supportsPolygon = true;
readonly _supportsGnosis = true;
readonly _supportsArbitrum = true;
readonly _supportsThorchainInfo = true;
readonly _supportsThorchain = true;

_isLedger = true;

Expand Down Expand Up @@ -509,6 +512,9 @@ export class LedgerHDWallet implements core.HDWallet, core.BTCWallet, core.ETHWa
await this.validateCurrentApp(msg.coin);
return btc.btcGetAddress(this.transport, msg);
}
public thorchainGetAddress(msg: core.ThorchainGetAddress): Promise<string | null> {
return thorchain.thorchainGetAddress(this.transport, msg);
}

public async btcSignTx(msg: core.BTCSignTxLedger): Promise<core.BTCSignedTx> {
await this.validateCurrentApp(msg.coin);
Expand Down
113 changes: 113 additions & 0 deletions packages/hdwallet-ledger/src/thorchain/common.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
export enum ErrorCode {
NoError = 0x9000,
}

export const CLA = 0x55;
export const CHUNK_SIZE = 250;
export const APP_KEY = "CSM";

export const INS = {
GET_VERSION: 0x00,
INS_PUBLIC_KEY_SECP256K1: 0x01, // Obsolete
SIGN_SECP256K1: 0x02,
GET_ADDR_SECP256K1: 0x04,
};

export const PAYLOAD_TYPE = {
INIT: 0x00,
ADD: 0x01,
LAST: 0x02,
};

export const P1_VALUES = {
ONLY_RETRIEVE: 0x00,
SHOW_ADDRESS_IN_DEVICE: 0x01,
};

const ERROR_DESCRIPTION = {
1: "U2F: Unknown",
2: "U2F: Bad request",
3: "U2F: Configuration unsupported",
4: "U2F: Device Ineligible",
5: "U2F: Timeout",
14: "Timeout",
0x9000: "No errors",
0x9001: "Device is busy",
0x6802: "Error deriving keys",
0x6400: "Execution Error",
0x6700: "Wrong Length",
0x6982: "Empty Buffer",
0x6983: "Output buffer too small",
0x6984: "Data is invalid",
0x6985: "Conditions not satisfied",
0x6986: "Transaction rejected",
0x6a80: "Bad key handle",
0x6b00: "Invalid P1/P2",
0x6d00: "Instruction not supported",
0x6e00: "App does not seem to be open",
0x6f00: "Unknown error",
0x6f01: "Sign/verify error",
};

export function errorCodeToString(statusCode: any) {
if (statusCode in ERROR_DESCRIPTION) return ERROR_DESCRIPTION[statusCode as 1];
return `Unknown Status Code: ${statusCode}`;
}

export function processErrorResponse(response: any) {
if (response) {
if (
typeof response === "object" &&
response !== null &&
!(response instanceof Array) &&
!(response instanceof Date)
) {
if (Object.prototype.hasOwnProperty.call(response, "statusCode")) {
return {
return_code: response.statusCode,
error_message: errorCodeToString(response.statusCode),
};
}

if (
Object.prototype.hasOwnProperty.call(response, "return_code") &&
Object.prototype.hasOwnProperty.call(response, "error_message")
) {
return response;
}
}
return {
return_code: 0xffff,
error_message: response.toString(),
};
}

return {
return_code: 0xffff,
error_message: response.toString(),
};
}

export async function getVersion(transport: any) {
return transport.send(CLA, INS.GET_VERSION, 0, 0).then((response: any) => {
const errorCodeData = response.slice(-2);
const returnCode = errorCodeData[0] * 256 + errorCodeData[1];

let targetId = 0;
if (response.length >= 9) {
targetId = (response[5] << 24) + (response[6] << 16) + (response[7] << 8) + (response[8] << 0);
}

return {
return_code: returnCode,
error_message: errorCodeToString(returnCode),
// ///
test_mode: response[0] !== 0,
major: response[1],
minor: response[2],
patch: response[3],
device_locked: response[4] === 1,
target_id: targetId.toString(16),
};
}, processErrorResponse);
}
68 changes: 68 additions & 0 deletions packages/hdwallet-ledger/src/thorchain/helpers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { CLA, ErrorCode, errorCodeToString, INS, PAYLOAD_TYPE, processErrorResponse } from "./common";

const signSendChunkv1 = async (app: any, chunkIdx: any, chunkNum: any, chunk: any) => {
return app.transport
.send(CLA, INS.SIGN_SECP256K1, chunkIdx, chunkNum, chunk, [ErrorCode.NoError, 0x6984, 0x6a80])
.then((response: any) => {
const errorCodeData = response.slice(-2);
const returnCode = errorCodeData[0] * 256 + errorCodeData[1];
let errorMessage = errorCodeToString(returnCode);

if (returnCode === 0x6a80 || returnCode === 0x6984) {
errorMessage = `${errorMessage} : ${response.slice(0, response.length - 2).toString("ascii")}`;
}

let signature = null;
if (response.length > 2) {
signature = response.slice(0, response.length - 2);
}

return {
signature,
return_code: returnCode,
error_message: errorMessage,
};
}, processErrorResponse);
};

export const serializePathv2 = (path: any) => {
if (!path || path.length !== 5) {
throw new Error("Invalid path.");
}

const buf = Buffer.alloc(20);
buf.writeUInt32LE(0x80000000 + path[0], 0);
buf.writeUInt32LE(0x80000000 + path[1], 4);
buf.writeUInt32LE(0x80000000 + path[2], 8);
buf.writeUInt32LE(path[3], 12);
buf.writeUInt32LE(path[4], 16);

return buf;
};

export const signSendChunkv2 = async (app: any, chunkIdx: any, chunkNum: any, chunk: any) => {
let payloadType = PAYLOAD_TYPE.ADD;
if (chunkIdx === 1) {
payloadType = PAYLOAD_TYPE.INIT;
}
if (chunkIdx === chunkNum) {
payloadType = PAYLOAD_TYPE.LAST;
}

return signSendChunkv1(app, payloadType, 0, chunk);
};

export const publicKeyv2 = async (app: any, data: any) => {
return app.transport.send(CLA, INS.GET_ADDR_SECP256K1, 0, 0, data, [ErrorCode.NoError]).then((response: any) => {
const errorCodeData = response.slice(-2);
const returnCode = errorCodeData[0] * 256 + errorCodeData[1];
const compressedPk = Buffer.from(response.slice(0, 33));

return {
pk: "OBSOLETE PROPERTY",
compressed_pk: compressedPk,
return_code: returnCode,
error_message: errorCodeToString(returnCode),
};
}, processErrorResponse);
};
Loading

0 comments on commit 7d28d0b

Please sign in to comment.