UNPKG

@zondax/ledger-polkadot

Version:

TS / Node API for Polkadot apps running on Ledger Nano S/S+/X and Stax

233 lines (232 loc) 9.66 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.PolkadotApp = void 0; const utils_1 = require("./utils"); class PolkadotApp { transport; cla = 0x90; constructor(transport) { if (transport == null) throw new Error("Transport has not been defined"); this.transport = transport; } getChunks(path, blob, metadata) { if (blob.length >= 2 ** 16) throw new Error("Blob length should be smaller than 2^16 bytes"); const chunks = []; const bip44Path = (0, utils_1.serializePath)(path); const blobLen = Buffer.alloc(2); blobLen.writeUInt16LE(blob.length); const firstChunk = Buffer.concat([bip44Path, blobLen]); chunks.push(firstChunk); if (metadata == null) chunks.push(...(0, utils_1.splitChunks)(blob)); else chunks.push(...(0, utils_1.splitChunks)(Buffer.concat([blob, metadata]))); return chunks; } async getVersion() { return await this.transport.send(this.cla, 0 /* INS.GET_VERSION */, 0, 0).then((response) => { const errorCodeData = response.subarray(-2); const returnCode = errorCodeData[0] * 256 + errorCodeData[1]; const retObj = { returnCode, errorMessage: (0, utils_1.errorCodeToString)(returnCode), locked: false, testMode: false, major: 0, minor: 0, patch: 0, targetId: 0, }; // 12 bytes + 2 error code // if it's not, data is invalid if (response.length !== 14) { retObj.returnCode = 27012 /* ERROR_CODE.InvalidData */; retObj.errorMessage = (0, utils_1.errorCodeToString)(27012 /* ERROR_CODE.InvalidData */); return retObj; } retObj.testMode = response[0] !== 0; retObj.major = response[1] * 256 + response[2]; retObj.minor = response[3] * 256 + response[4]; retObj.patch = response[5] * 256 + response[6]; retObj.locked = response[7] === 1; retObj.targetId = (response[8] << 24) + (response[9] << 16) + (response[10] << 8) + (response[11] << 0); return retObj; }, (err) => { const { returnCode, errorMessage } = (0, utils_1.processErrorResponse)(err); return { returnCode, errorMessage, locked: false, testMode: false, major: 0, minor: 0, patch: 0, targetId: 0, }; }); } async appInfo() { return await this.transport.send(0xb0, 0x01, 0, 0).then((response) => { const errorCodeData = response.subarray(-2); const returnCode = errorCodeData[0] * 256 + errorCodeData[1]; if (response[0] !== 1) { // Ledger responds with format ID 1. There is no spec for any format != 1 return { returnCode: 0x9001, errorMessage: "Response format ID not recognized", appName: "err", appVersion: "err", flagsValue: 0, flags: { recovery: false, signedMcuCode: false, onboarded: false, issuerTrusted: false, customCaTrusted: false, hsmInitialized: false, factoryFilled: false, pinValidated: false, }, }; } const appNameLen = response[1]; const appName = response.subarray(2, 2 + appNameLen).toString("ascii"); let idx = 2 + appNameLen; const appVersionLen = response[idx]; idx += 1; const appVersion = response.subarray(idx, idx + appVersionLen).toString("ascii"); idx += appVersionLen; idx += 1; const flagsValue = response[idx]; return { returnCode, errorMessage: (0, utils_1.errorCodeToString)(returnCode), appName: appName ?? "err", appVersion: appVersion ?? "err", flagsValue, flags: { recovery: (flagsValue & 0b1) === 1, signedMcuCode: (flagsValue & 0b10) === 1, onboarded: (flagsValue & 0b100) === 1, issuerTrusted: (flagsValue & 0b1000) === 1, customCaTrusted: (flagsValue & 0b10000) === 1, hsmInitialized: (flagsValue & 0b100000) === 1, factoryFilled: (flagsValue & 0b1000000) === 1, pinValidated: (flagsValue & 0b10000000) === 1, }, }; }, (err) => { const { returnCode, errorMessage } = (0, utils_1.processErrorResponse)(err); return { returnCode, errorMessage, appName: "err", appVersion: "err", flagsValue: 0, flags: { recovery: false, signedMcuCode: false, onboarded: false, issuerTrusted: false, customCaTrusted: false, hsmInitialized: false, factoryFilled: false, pinValidated: false, }, }; }); } async getAddress(path, ss58prefix, showAddrInDevice = false) { // needs to be integer, and up to 65535 if (!Number.isInteger(ss58prefix) && ss58prefix >> 16 !== 0) { throw new Error(`Unexpected ss58prefix ${ss58prefix}. Needs to be up to 2^16`); } const bip44Path = (0, utils_1.serializePath)(path); const payload = Buffer.alloc(bip44Path.length + 2); bip44Path.copy(payload); payload.writeUInt16LE(ss58prefix, payload.length - 2); const p1 = showAddrInDevice ? 1 /* P1_VALUES.SHOW_ADDR */ : 0 /* P1_VALUES.GET_ADDR */; return await this.transport.send(this.cla, 1 /* INS.GET_ADDR */, p1, 0, payload).then((response) => { const errorCodeData = response.subarray(-2); const errorCode = errorCodeData[0] * 256 + errorCodeData[1]; return { pubKey: response.subarray(0, 32), address: response.subarray(32, response.length - 2).toString("ascii"), returnCode: errorCode, errorMessage: (0, utils_1.errorCodeToString)(errorCode), }; }, (err) => { const { returnCode, errorMessage } = (0, utils_1.processErrorResponse)(err); return { returnCode, errorMessage, pubKey: Buffer.alloc(0), address: "", }; }); } async signSendChunk(chunkIdx, chunkNum, chunk, ins = 2 /* INS.SIGN */) { let payloadType = 1 /* PAYLOAD_TYPE.ADD */; if (chunkIdx === 1) { payloadType = 0 /* PAYLOAD_TYPE.INIT */; } if (chunkIdx === chunkNum) { payloadType = 2 /* PAYLOAD_TYPE.LAST */; } return await this.transport .send(this.cla, ins, payloadType, 0, chunk, [36864 /* ERROR_CODE.NoError */, 27012 /* ERROR_CODE.InvalidData */]) .then((response) => { const errorCodeData = response.subarray(-2); const returnCode = errorCodeData[0] * 256 + errorCodeData[1]; let errorMessage = (0, utils_1.errorCodeToString)(returnCode); let signature = Buffer.alloc(0); if (response.length > 2) { if (returnCode === 0x6984) { errorMessage = response.subarray(0, response.length - 2).toString("ascii"); } else { signature = response.subarray(0, response.length - 2); } } return { signature, returnCode, errorMessage, }; }, (err) => { const { returnCode, errorMessage } = (0, utils_1.processErrorResponse)(err); return { returnCode, errorMessage, signature: Buffer.alloc(0), }; }); } async signImpl(path, ins, blob, metadata) { const chunks = this.getChunks(path, blob, metadata); return await this.signSendChunk(1, chunks.length, chunks[0], ins).then(async (result) => { for (let i = 1; i < chunks.length; i += 1) { result = await this.signSendChunk(1 + i, chunks.length, chunks[i], ins); if (result.returnCode !== 36864 /* ERROR_CODE.NoError */) break; } return result; }, (err) => { const { returnCode, errorMessage } = (0, utils_1.processErrorResponse)(err); return { returnCode, errorMessage, signature: Buffer.alloc(0), }; }); } async sign(path, blob, metadata) { return await this.signImpl(path, 2 /* INS.SIGN */, blob, metadata); } async signRaw(path, blob) { return await this.signImpl(path, 3 /* INS.SIGN_RAW */, blob); } } exports.PolkadotApp = PolkadotApp;