UNPKG

@zondax/ledger-substrate

Version:

TS / Node API for Substrate/Polkadot based apps running on Ledger devices

383 lines 15.4 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.SubstrateApp = void 0; const common_1 = require("./common"); /** * Class representing a Substrate application. */ class SubstrateApp { /** * Create a SubstrateApp instance. * @param transport - The transport instance. * @param cla - The CLA value. * @param slip0044 - The SLIP-0044 value. */ constructor(transport, cla, slip0044) { if (transport == null) { throw new Error('Transport has not been defined'); } this.transport = transport; this.cla = cla; this.slip0044 = slip0044; } /** * Serialize the BIP44 path. * @param slip0044 - The SLIP-0044 value. * @param account - The account index. * @param change - The change index. * @param addressIndex - The address index. * @returns The serialized path. */ static serializePath(slip0044, account, change, addressIndex) { if (!Number.isInteger(account)) throw new Error('Input must be an integer'); if (!Number.isInteger(change)) throw new Error('Input must be an integer'); if (!Number.isInteger(addressIndex)) throw new Error('Input must be an integer'); const buf = Buffer.alloc(20); buf.writeUInt32LE(0x8000002c, 0); buf.writeUInt32LE(slip0044, 4); buf.writeUInt32LE(account, 8); buf.writeUInt32LE(change, 12); buf.writeUInt32LE(addressIndex, 16); return buf; } /** * Split a message into chunks. * @param message - The message to split. * @returns The message chunks. */ static GetChunks(message) { const chunks = []; const buffer = Buffer.from(message); for (let i = 0; i < buffer.length; i += common_1.CHUNK_SIZE) { let end = i + common_1.CHUNK_SIZE; if (i > buffer.length) { end = buffer.length; } chunks.push(buffer.subarray(i, end)); } return chunks; } /** * Get chunks for signing. * @param slip0044 - The SLIP-0044 value. * @param account - The account index. * @param change - The change index. * @param addressIndex - The address index. * @param message - The message to sign. * @returns The chunks for signing. */ static signGetChunks(slip0044, account, change, addressIndex, message) { const chunks = []; const bip44Path = SubstrateApp.serializePath(slip0044, account, change, addressIndex); chunks.push(bip44Path); chunks.push(...SubstrateApp.GetChunks(message)); return chunks; } /** * Get the version of the application. * @returns The version response. */ async getVersion() { try { return await (0, common_1.getVersion)(this.transport, this.cla); } catch (e) { return (0, common_1.processErrorResponse)(e); } } /** * Get application information. * @returns The application information. */ 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]; let appName = ''; let appVersion = ''; let flagLen = 0; let flagsValue = 0; if (response[0] !== 1) { // Ledger responds with format ID 1. There is no spec for any format != 1 return { return_code: 0x9001, error_message: 'response format ID not recognized', }; } else { const appNameLen = response[1]; appName = response.subarray(2, 2 + appNameLen).toString('ascii'); let idx = 2 + appNameLen; const appVersionLen = response[idx]; idx += 1; appVersion = response.subarray(idx, idx + appVersionLen).toString('ascii'); idx += appVersionLen; const appFlagsLen = response[idx]; idx += 1; flagLen = appFlagsLen; flagsValue = response[idx]; } return { return_code: returnCode, error_message: (0, common_1.errorCodeToString)(returnCode), appName: appName === '' || 'err', appVersion: appVersion === '' || 'err', flagLen, flagsValue, flag_recovery: (flagsValue & 1) !== 0, flag_signed_mcu_code: (flagsValue & 2) !== 0, flag_onboarded: (flagsValue & 4) !== 0, flag_pin_validated: (flagsValue & 128) !== 0, }; }, common_1.processErrorResponse); } /** * Get the address. * @param account - The account index. * @param change - The change index. * @param addressIndex - The address index. * @param [requireConfirmation=false] - Whether confirmation is required. * @param [scheme=SCHEME.ED25519] - The scheme. * @returns The address response. */ async getAddress(account, change, addressIndex, requireConfirmation = false, scheme = 0 /* SCHEME.ED25519 */) { const bip44Path = SubstrateApp.serializePath(this.slip0044, account, change, addressIndex); let p1 = 0; if (requireConfirmation) p1 = 1; let p2 = 0; if (!isNaN(scheme)) p2 = scheme; return await this.transport.send(this.cla, 1 /* INS.GET_ADDR */, p1, p2, bip44Path).then(response => { const errorCodeData = response.subarray(-2); const errorCode = errorCodeData[0] * 256 + errorCodeData[1]; let pubkeyLen = 32; if (scheme == 2 /* SCHEME.ECDSA */) { pubkeyLen = 33; } return { pubKey: response.subarray(0, pubkeyLen).toString('hex'), address: response.subarray(pubkeyLen, response.length - 2).toString('ascii'), return_code: errorCode, error_message: (0, common_1.errorCodeToString)(errorCode), }; }, common_1.processErrorResponse); } /** * Send a chunk for signing. * @private * @param chunkIdx - The chunk index. * @param chunkNum - The total number of chunks. * @param chunk - The chunk data. * @param [scheme=SCHEME.ED25519] - The scheme. * @param [ins=INS.SIGN] - The instruction. * @returns The response. */ async signSendChunk(chunkIdx, chunkNum, chunk, scheme = 0 /* SCHEME.ED25519 */, 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 */; } let p2 = 0; if (!isNaN(scheme)) p2 = scheme; return await this.transport.send(this.cla, ins, payloadType, p2, chunk, [36864 /* ERROR_CODE.NoError */, 0x6984, 0x6a80]).then(response => { const errorCodeData = response.subarray(-2); const returnCode = errorCodeData[0] * 256 + errorCodeData[1]; let errorMessage = (0, common_1.errorCodeToString)(returnCode); let signature = null; if (returnCode === 0x6a80 || returnCode === 0x6984) { errorMessage = response.subarray(0, response.length - 2).toString('ascii'); } else if (response.length > 2) { signature = response.subarray(0, response.length - 2); } return { signature, return_code: returnCode, error_message: errorMessage, }; }, common_1.processErrorResponse); } /** * Implementation of the signing process. * @private * @param account - The account index. * @param change - The change index. * @param addressIndex - The address index. * @param message - The message to sign. * @param ins - The instruction. * @param [scheme=SCHEME.ED25519] - The scheme. * @returns The signing response. */ async signImpl(account, change, addressIndex, message, ins, scheme = 0 /* SCHEME.ED25519 */) { const chunks = SubstrateApp.signGetChunks(this.slip0044, account, change, addressIndex, message); return await this.signSendChunk(1, chunks.length, chunks[0], scheme, ins).then(async () => { let result; for (let i = 1; i < chunks.length; i += 1) { result = await this.signSendChunk(1 + i, chunks.length, chunks[i], scheme, ins); if (result.return_code !== 36864 /* ERROR_CODE.NoError */) { break; } } return { return_code: result.return_code, error_message: result.error_message, signature: result.signature, }; }, common_1.processErrorResponse); } /** * Sign a message. * @param account - The account index. * @param change - The change index. * @param addressIndex - The address index. * @param message - The message to sign. * @param [scheme=SCHEME.ED25519] - The scheme. * @returns The signing response. */ async sign(account, change, addressIndex, message, scheme = 0 /* SCHEME.ED25519 */) { return await this.signImpl(account, change, addressIndex, message, 2 /* INS.SIGN */, scheme); } /** * Sign a raw message. * @param account - The account index. * @param change - The change index. * @param addressIndex - The address index. * @param message - The message to sign. * @param [scheme=SCHEME.ED25519] - The scheme. * @returns The signing response. */ async signRaw(account, change, addressIndex, message, scheme = 0 /* SCHEME.ED25519 */) { return await this.signImpl(account, change, addressIndex, message, 3 /* INS.SIGN_RAW */, scheme); } /** * @deprecated This function is deprecated and will be removed in future versions. * Get the allowlist public key. * @returns The allowlist public key response. */ async getAllowlistPubKey() { return await this.transport.send(this.cla, 144 /* INS.ALLOWLIST_GET_PUBKEY */, 0, 0).then(response => { const errorCodeData = response.subarray(-2); const returnCode = errorCodeData[0] * 256 + errorCodeData[1]; console.log(response); const pubkey = response.subarray(0, 32); // 32 bytes + 2 error code if (response.length !== 34) { return { return_code: 0x6984, error_message: (0, common_1.errorCodeToString)(0x6984), }; } return { return_code: returnCode, error_message: (0, common_1.errorCodeToString)(returnCode), pubkey, }; }, common_1.processErrorResponse); } /** * @deprecated This function is deprecated and will be removed in future versions. * Set the allowlist public key. * @param pk - The public key. * @returns The response. */ async setAllowlistPubKey(pk) { return await this.transport.send(this.cla, 145 /* INS.ALLOWLIST_SET_PUBKEY */, 0, 0, pk).then(response => { const errorCodeData = response.subarray(-2); const returnCode = errorCodeData[0] * 256 + errorCodeData[1]; return { return_code: returnCode, error_message: (0, common_1.errorCodeToString)(returnCode), }; }, common_1.processErrorResponse); } /** * @deprecated This function is deprecated and will be removed in future versions. * Get the allowlist hash. * @returns The allowlist hash response. */ async getAllowlistHash() { return await this.transport.send(this.cla, 146 /* INS.ALLOWLIST_GET_HASH */, 0, 0).then(response => { const errorCodeData = response.subarray(-2); const returnCode = errorCodeData[0] * 256 + errorCodeData[1]; console.log(response); const hash = response.subarray(0, 32); // 32 bytes + 2 error code if (response.length !== 34) { return { return_code: 0x6984, error_message: (0, common_1.errorCodeToString)(0x6984), }; } return { return_code: returnCode, error_message: (0, common_1.errorCodeToString)(returnCode), hash, }; }, common_1.processErrorResponse); } /** * @deprecated This function is deprecated and will be removed in future versions. * Send a chunk for uploading the allowlist. * @param chunkIdx - The chunk index. * @param chunkNum - The total number of chunks. * @param chunk - The chunk data. * @returns The response. */ async uploadSendChunk(chunkIdx, chunkNum, chunk) { 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, 147 /* INS.ALLOWLIST_UPLOAD */, payloadType, 0, chunk, [36864 /* ERROR_CODE.NoError */]).then(response => { const errorCodeData = response.subarray(-2); const returnCode = errorCodeData[0] * 256 + errorCodeData[1]; const errorMessage = (0, common_1.errorCodeToString)(returnCode); return { return_code: returnCode, error_message: errorMessage, }; }, common_1.processErrorResponse); } /** * @deprecated This function is deprecated and will be removed in future versions. * Upload the allowlist. * @param message - The allowlist message. * @returns The response. */ async uploadAllowlist(message) { const chunks = []; chunks.push(Buffer.from([0])); chunks.push(...SubstrateApp.GetChunks(message)); return await this.uploadSendChunk(1, chunks.length, chunks[0]).then(async (result) => { if (result.return_code !== 36864 /* ERROR_CODE.NoError */) { return { return_code: result.return_code, error_message: result.error_message, }; } for (let i = 1; i < chunks.length; i += 1) { result = await this.uploadSendChunk(1 + i, chunks.length, chunks[i]); if (result.return_code !== 36864 /* ERROR_CODE.NoError */) { break; } } return { return_code: result.return_code, error_message: result.error_message, }; }, common_1.processErrorResponse); } } exports.SubstrateApp = SubstrateApp; //# sourceMappingURL=substrate_app.js.map