@zondax/ledger-mina-js
Version:
Node API for the Mina App (Ledger Nano S, S+, X, Stax and Flex)
297 lines • 11.9 kB
JavaScript
;
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