UNPKG

@zondax/ledger-mina-js

Version:

Node API for the Mina App (Ledger Nano S, S+, X, Stax and Flex)

297 lines 11.9 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.MinaApp = void 0; const ledger_js_1 = __importStar(require("@zondax/ledger-js")); const consts_1 = require("./consts"); class MinaApp extends ledger_js_1.default { constructor(transport) { super(transport, MinaApp._params); if (!this.transport) { throw new Error("Transport has not been defined"); } } async getAppName() { try { const version = await this.getAppVersion(); return { name: "Mina", version: version.version, returnCode: "9000", }; } catch (error) { const respError = (0, ledger_js_1.processErrorResponse)(error); return { name: undefined, version: undefined, returnCode: respError.returnCode.toString(), message: respError.errorMessage, }; } } async getAppVersion() { try { const responseBuffer = await this.transport.send(this.CLA, this.INS.GET_VERSION, 0, 0); const response = (0, ledger_js_1.processResponse)(responseBuffer); if (response.length() !== 3) { throw new Error("Response length is not valid"); } const version = "" + response.readBytes(1).readUInt8() + "." + response.readBytes(1).readUInt8() + "." + response.readBytes(1).readUInt8(); return { version, returnCode: "9000", }; } catch (error) { const respError = (0, ledger_js_1.processErrorResponse)(error); return { version: null, returnCode: respError.returnCode.toString(), message: respError.errorMessage, }; } } async getAddress(account, showAddrInDevice = true) { if (!Number.isInteger(account)) { return { publicKey: null, returnCode: "-5", message: "Account number must be an Integer", }; } if (account === undefined) { return { publicKey: null, returnCode: "-1", message: "Account number is required", }; } const accountBuf = Buffer.from(account.toString(16).padStart(8, "0"), "hex"); const p1 = showAddrInDevice ? 0 /* P1_VALUES.SHOW_ADDRESS_IN_DEVICE */ : 1 /* P1_VALUES.ONLY_RETRIEVE */; try { const responseBuffer = await this.transport.send(this.CLA, this.INS.GET_ADDR, p1, 0, accountBuf); const response = (0, ledger_js_1.processResponse)(responseBuffer); return { publicKey: response.readBytes(consts_1.PUBKEYLEN).toString(), returnCode: "9000", }; } catch (error) { const respError = (0, ledger_js_1.processErrorResponse)(error); return { publicKey: null, returnCode: respError.returnCode.toString(), message: respError.errorMessage, }; } } async signTransaction({ txType, senderAccount, senderAddress, receiverAddress, amount, fee, nonce, validUntil, memo, networkId, }) { if (isNaN(txType) || isNaN(senderAccount) || !senderAddress || !receiverAddress || (!amount && txType === 0) /* PAYMENT */ || !fee || !Number.isInteger(amount) || !Number.isInteger(fee) || isNaN(nonce) || isNaN(networkId)) { return { signature: null, returnCode: "-1", message: "Missing or wrong arguments", }; } if (memo && memo.length > 32) { return { signature: null, returnCode: "-3", message: "Memo field too long", }; } if (fee < 1e6) { return { signature: null, returnCode: "-4", message: "Fee too small", }; } const apdu = this.createTXApdu(txType, senderAccount, senderAddress, receiverAddress, amount, fee, nonce, validUntil, memo, networkId); const apduBuffer = Buffer.from(apdu, "hex"); const statusList = [0x9000, 0x6986]; if (apduBuffer.length > 256) { return { signature: null, returnCode: "-2", message: "data length > 256 bytes", statusText: "DATA_TOO_BIG", }; } try { const responseBuffer = await this.transport.send(this.CLA, this.INS.SIGN_TX, 0, 0, apduBuffer, statusList); const response = (0, ledger_js_1.processResponse)(responseBuffer); const signature = response.readBytes(response.length()).toString("hex"); return { signature, returnCode: "9000", }; } catch (e) { const respError = (0, ledger_js_1.processErrorResponse)(e); return { signature: null, returnCode: respError.returnCode.toString(), message: respError.errorMessage, }; } } async signMessage(account, networkId, message) { if (message.length === 0) { return { field: null, scalar: null, raw_signature: null, signed_message: null, returnCode: "-6", message: "Message is empty", }; } if (message.length > 255) { return { field: null, scalar: null, raw_signature: null, signed_message: null, returnCode: "-7", message: "Message too long", }; } try { const accountHex = Buffer.from(account.toString(16).padStart(8, "0"), "hex"); const networkIdHex = Buffer.from(networkId.toString(16).padStart(2, "0"), "hex"); const messageHex = Buffer.from(message, "utf8"); // Calculate total buffer length const totalLength = accountHex.length + networkIdHex.length + messageHex.length; // Create buffer with total length const dataTx = Buffer.concat([accountHex, networkIdHex, messageHex], totalLength); const responseBuffer = await this.transport.send(this.CLA, this.INS.SIGN_MSG, 0, 0, dataTx); const response = (0, ledger_js_1.processResponse)(responseBuffer); // Validate minimum buffer length (64 bytes for signature + 1 byte for message length) if (response.length() < 65) { throw new Error("Response buffer too short"); } // First read the signature (64 bytes) const signature = response.readBytes(64).toString("hex"); const sigLength = signature.length; const field_extracted = signature.substring(0, sigLength / 2); const scalar_extracted = signature.substring(sigLength / 2, sigLength); // Then read the message length (1 byte) const messageLength = response.readBytes(1).readUInt8(); // Validate remaining buffer length against message length if (response.length() < messageLength) { throw new Error(`Response buffer too short for message: expected ${messageLength} bytes but only ${response.length()} remaining`); } // Finally read the message const returnedMessage = response .readBytes(messageLength) .toString("utf8"); return { field: BigInt("0x" + field_extracted).toString(), scalar: BigInt("0x" + scalar_extracted).toString(), raw_signature: signature, signed_message: returnedMessage, returnCode: "9000", }; } catch (e) { const respError = (0, ledger_js_1.processErrorResponse)(e); return { field: null, scalar: null, raw_signature: null, signed_message: null, returnCode: respError.returnCode.toString(), message: respError.errorMessage, }; } } createTXApdu(txType, senderAccount, senderAddress, receiverAddress, amount, fee, nonce, validUntil = 4294967295, memo = "", networkId) { const senderBip44AccountHex = Buffer.from(senderAccount.toString(16).padStart(8, "0"), "hex").toString("hex"); const senderAddressHex = Buffer.from(senderAddress, "ascii").toString("hex"); const receiverHex = Buffer.from(receiverAddress, "ascii").toString("hex"); const amountHex = Buffer.from(amount.toString(16).padStart(16, "0"), "hex").toString("hex"); const feeHex = Buffer.from(fee.toString(16).padStart(16, "0"), "hex").toString("hex"); const nonceHex = Buffer.from(Number(nonce).toString(16).toUpperCase().padStart(8, "0"), "hex").toString("hex"); const validUntilHex = Buffer.from(validUntil.toString(16).padStart(8, "0"), "hex").toString("hex"); const memoHex = Buffer.from(memo.padEnd(32, "\0"), "utf8").toString("hex"); const tagHex = Buffer.from(txType.toString(16).padStart(2, "0"), "hex").toString("hex"); const networkIdHex = Buffer.from(networkId.toString(16).padStart(2, "0"), "hex").toString("hex"); const apduMessage = senderBip44AccountHex + senderAddressHex + receiverHex + amountHex + feeHex + nonceHex + validUntilHex + memoHex + tagHex + networkIdHex; return apduMessage; } } exports.MinaApp = MinaApp; MinaApp._INS = { GET_VERSION: 0x01, GET_ADDR: 0x02, SIGN_TX: 0x03, TEST_CRYPTO: 0x04, SIGN_MSG: 0x05, }; MinaApp._params = { cla: 0xe0, ins: { ...MinaApp._INS }, p1Values: { ONLY_RETRIEVE: 0x00, SHOW_ADDRESS_IN_DEVICE: 0x01 }, chunkSize: 250, requiredPathLengths: [5], }; //# sourceMappingURL=app.js.map