@zondax/ledger-icp
Version:
Node API for the Internet Computer App (Ledger Nano S/X/S+/Stax/Flex)
352 lines • 16.2 kB
JavaScript
"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;
};
})();
var __exportStar = (this && this.__exportStar) || function(m, exports) {
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.SIGN_VALUES_P2 = void 0;
/** ******************************************************************************
* (c) 2019-2020 Zondax AG
* (c) 2016-2017 Ledger
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
******************************************************************************* */
const consts_1 = require("./consts");
const ledger_js_1 = __importStar(require("@zondax/ledger-js"));
const helper_1 = require("./helper");
__exportStar(require("./types"), exports);
var consts_2 = require("./consts");
Object.defineProperty(exports, "SIGN_VALUES_P2", { enumerable: true, get: function () { return consts_2.SIGN_VALUES_P2; } });
class InternetComputerApp extends ledger_js_1.default {
constructor(transport) {
if (transport == null)
throw new Error("Transport has not been defined");
const params = {
cla: 0x11,
ins: {
GET_VERSION: 0x00,
GET_ADDR_SECP256K1: 0x01,
SIGN_SECP256K1: 0x02,
SIGN_COMBINED: 0x03,
SAVE_CONSENT: 0x04,
SAVE_CANISTER_CALL: 0x05,
SAVE_CERITIFACE_AND_VERIFY: 0x06,
GET_REGISTRY_LEN: 0x07,
GET_TOKEN_I: 0x08,
},
p1Values: {
ONLY_RETRIEVE: 0x00,
SHOW_ADDRESS_IN_DEVICE: 0x01,
},
acceptedPathLengths: [5],
chunkSize: 250,
};
super(transport, params);
}
async getAddressAndPubKey(path) {
const serializedPath = this.serializePath(path);
return await this.transport
.send(this.CLA, this.INS.GET_ADDR_SECP256K1, this.P1_VALUES.ONLY_RETRIEVE, 0, serializedPath, [0x9000])
.then(helper_1.processGetAddrResponse, ledger_js_1.processErrorResponse);
}
async showAddressAndPubKey(path) {
const serializedPath = this.serializePath(path);
return await this.transport
.send(this.CLA, this.INS.GET_ADDR_SECP256K1, this.P1_VALUES.SHOW_ADDRESS_IN_DEVICE, 0, serializedPath, [36864 /* LedgerError.NoErrors */])
.then(helper_1.processGetAddrResponse, ledger_js_1.processErrorResponse);
}
async signSendChunk(chunkIdx, chunkNum, chunk, txtype, ins) {
let payloadType = ledger_js_1.PAYLOAD_TYPE.ADD;
if (chunkIdx === 1) {
payloadType = ledger_js_1.PAYLOAD_TYPE.INIT;
}
if (chunkIdx === chunkNum) {
payloadType = ledger_js_1.PAYLOAD_TYPE.LAST;
}
return await this.transport
.send(this.CLA, ins, payloadType, txtype, chunk, [
36864 /* LedgerError.NoErrors */,
27012 /* LedgerError.DataIsInvalid */,
27264 /* LedgerError.BadKeyHandle */,
28417 /* LedgerError.SignVerifyError */,
])
.then((response) => {
const errorCodeData = response.subarray(-2);
const returnCode = errorCodeData[0] * 256 + errorCodeData[1];
let errorMessage = (0, ledger_js_1.errorCodeToString)(returnCode);
let preSignHash = Buffer.alloc(0);
let signatureRS = Buffer.alloc(0);
let signatureDER = Buffer.alloc(0);
if (returnCode === 27264 /* LedgerError.BadKeyHandle */ ||
returnCode === 27012 /* LedgerError.DataIsInvalid */ ||
returnCode === 28417 /* LedgerError.SignVerifyError */) {
errorMessage = `${errorMessage} : ${response.subarray(0, response.length - 2).toString("ascii")}`;
}
if (returnCode === 36864 /* LedgerError.NoErrors */ && response.length > 2) {
preSignHash = response.subarray(0, consts_1.PREHASH_LEN);
signatureRS = response.subarray(consts_1.PREHASH_LEN, consts_1.PREHASH_LEN + consts_1.SIGRSLEN);
signatureDER = response.subarray(consts_1.PREHASH_LEN + consts_1.SIGRSLEN + 1, response.length - 2);
return {
preSignHash,
signatureRS,
signatureDER,
returnCode,
errorMessage,
};
}
return {
returnCode,
errorMessage,
};
}, ledger_js_1.processErrorResponse);
}
async sign(path, message, txtype) {
const chunks = this.prepareChunks(path, message);
return await this.signSendChunk(1, chunks.length, chunks[0], txtype % 256, this.INS.SIGN_SECP256K1).then(async (response) => {
let result = {
returnCode: response.returnCode,
errorMessage: response.errorMessage,
};
for (let i = 1; i < chunks.length; i += 1) {
// eslint-disable-next-line no-await-in-loop
result = await this.signSendChunk(1 + i, chunks.length, chunks[i], txtype % 256, this.INS.SIGN_SECP256K1);
if (result.returnCode !== 36864 /* LedgerError.NoErrors */) {
break;
}
}
return result;
}, ledger_js_1.processErrorResponse);
}
async signSendChunkUpdateCall(chunkIdx, chunkNum, chunk, txtype) {
let payloadType = ledger_js_1.PAYLOAD_TYPE.ADD;
if (chunkIdx === 1) {
payloadType = ledger_js_1.PAYLOAD_TYPE.INIT;
}
if (chunkIdx === chunkNum) {
payloadType = ledger_js_1.PAYLOAD_TYPE.LAST;
}
return await this.transport
.send(this.CLA, this.INS.SIGN_COMBINED, payloadType, txtype, chunk, [
36864 /* LedgerError.NoErrors */,
27012 /* LedgerError.DataIsInvalid */,
27264 /* LedgerError.BadKeyHandle */,
28417 /* LedgerError.SignVerifyError */,
])
.then((response) => {
const errorCodeData = response.subarray(-2);
const returnCode = errorCodeData[0] * 256 + errorCodeData[1];
let errorMessage = (0, ledger_js_1.errorCodeToString)(returnCode);
let RequestHash = Buffer.alloc(0);
let RequestSignatureRS = Buffer.alloc(0);
let StatusReadHash = Buffer.alloc(0);
let StatusReadSignatureRS = Buffer.alloc(0);
if (returnCode === 27264 /* LedgerError.BadKeyHandle */ ||
returnCode === 27012 /* LedgerError.DataIsInvalid */ ||
returnCode === 28417 /* LedgerError.SignVerifyError */) {
errorMessage = `${errorMessage} : ${response.subarray(0, response.length - 2).toString("ascii")}`;
}
if (returnCode === 36864 /* LedgerError.NoErrors */ && response.length > 2) {
RequestHash = response.subarray(0, 32);
RequestSignatureRS = response.subarray(32, 96);
StatusReadHash = response.subarray(96, 128);
StatusReadSignatureRS = response.subarray(128, 192);
return {
RequestHash,
RequestSignatureRS,
StatusReadHash,
StatusReadSignatureRS,
returnCode,
errorMessage,
};
}
return {
returnCode,
errorMessage,
};
}, ledger_js_1.processErrorResponse);
}
async signUpdateCall(path, request, checkStatus, txtype) {
const message = Buffer.alloc(8 + request.byteLength + checkStatus.byteLength);
message.writeUInt32LE(checkStatus.byteLength, 0);
checkStatus.copy(message, 4);
message.writeUInt32LE(request.byteLength, 4 + checkStatus.byteLength);
request.copy(message, 8 + checkStatus.byteLength);
console.log(message.toString("hex"));
const chunks = this.prepareChunks(path, message);
return await this.signSendChunk(1, chunks.length, chunks[0], txtype % 256, this.INS.SIGN_SECP256K1).then(async (response) => {
let result = {
returnCode: response.returnCode,
errorMessage: response.errorMessage,
};
for (let i = 1; i < chunks.length; i += 1) {
// eslint-disable-next-line no-await-in-loop
result = await this.signSendChunkUpdateCall(1 + i, chunks.length, chunks[i], txtype % 256);
if (result.returnCode !== 36864 /* LedgerError.NoErrors */) {
break;
}
}
return result;
}, ledger_js_1.processErrorResponse);
}
async sendChunk(chunkIdx, chunkNum, chunk, ins) {
let payloadType = ledger_js_1.PAYLOAD_TYPE.ADD;
const p2 = 0;
if (chunkIdx === 1) {
payloadType = ledger_js_1.PAYLOAD_TYPE.INIT;
}
if (chunkIdx === chunkNum) {
payloadType = ledger_js_1.PAYLOAD_TYPE.LAST;
}
return await this.transport
.send(this.CLA, ins, payloadType, p2, chunk, [
36864 /* LedgerError.NoErrors */,
27012 /* LedgerError.DataIsInvalid */,
27264 /* LedgerError.BadKeyHandle */,
28417 /* LedgerError.SignVerifyError */,
])
.then((response) => {
const errorCodeData = response.subarray(-2);
const returnCode = errorCodeData[0] * 256 + errorCodeData[1];
let errorMessage = (0, ledger_js_1.errorCodeToString)(returnCode);
return {
returnCode,
errorMessage,
};
}, ledger_js_1.processErrorResponse);
}
async sendData(path, data, instruction) {
const data_buf = Buffer.from(data, "hex");
const chunks = this.prepareChunks(path, data_buf);
return await this.sendChunk(1, chunks.length, chunks[0], instruction).then(async (response) => {
let result = {
returnCode: response.returnCode,
errorMessage: response.errorMessage,
};
for (let i = 1; i < chunks.length; i += 1) {
// eslint-disable-next-line no-await-in-loop
result = await this.sendChunk(1 + i, chunks.length, chunks[i], instruction);
if (result.returnCode !== 36864 /* LedgerError.NoErrors */) {
break;
}
}
return result;
}, ledger_js_1.processErrorResponse);
}
async sendCertificateAndSig(path, data) {
const chunks = this.prepareChunks(path, Buffer.from(data, "hex"));
return await this.signSendChunk(1, chunks.length, chunks[0], 0, this.INS.SAVE_CERITIFACE_AND_VERIFY).then(async (response) => {
let result = {
returnCode: response.returnCode,
errorMessage: response.errorMessage,
};
for (let i = 1; i < chunks.length; i += 1) {
// eslint-disable-next-line no-await-in-loop
result = await this.signSendChunk(1 + i, chunks.length, chunks[i], 0, this.INS.SAVE_CERITIFACE_AND_VERIFY);
if (result.returnCode !== 36864 /* LedgerError.NoErrors */) {
break;
}
}
return result;
}, ledger_js_1.processErrorResponse);
}
async signBls(path, consent_request, canister_call, certificate) {
// Check if all strings are not empty
if (!consent_request || !canister_call || !certificate) {
throw new Error("All parameters must be non-empty strings");
}
let result;
// Send consent_request
result = await this.sendData(path, consent_request, this.INS.SAVE_CONSENT);
if (result.returnCode !== 36864 /* LedgerError.NoErrors */)
return result;
// Send canister_call
result = await this.sendData(path, canister_call, this.INS.SAVE_CANISTER_CALL);
if (result.returnCode !== 36864 /* LedgerError.NoErrors */)
return result;
// Send certificate and sign
return await this.sendCertificateAndSig(path, certificate);
}
async _getTokenRegistrySize() {
return await this.transport
.send(this.CLA, this.INS.GET_REGISTRY_LEN, 0, 0)
.then(helper_1.processTokenRegistrySizeResponse, ledger_js_1.processErrorResponse);
}
async tokenRegistry() {
const registrySize = await this._getTokenRegistrySize();
if (registrySize.returnCode !== 36864 /* LedgerError.NoErrors */ ||
registrySize.RegistrySize === undefined) {
return {
returnCode: registrySize.returnCode,
errorMessage: registrySize.errorMessage,
};
}
let tokenRegistry = [];
for (let i = 0; i < registrySize.RegistrySize; i += 1) {
const response = await this.transport
.send(this.CLA, this.INS.GET_TOKEN_I, i, 0)
.then((response) => (0, helper_1.processTokenInfoResponse)(response), (error) => (0, ledger_js_1.processErrorResponse)(error));
// Type guard to check if response is ResponseTokenInfo
if (!response ||
"tokenInfo" in response === false ||
response.returnCode !== 36864 /* LedgerError.NoErrors */) {
return {
returnCode: response?.returnCode || 25600 /* LedgerError.ExecutionError */,
errorMessage: response?.errorMessage || "Failed to get token info",
};
}
// At this point TypeScript knows response has tokenInfo
if (response.tokenInfo) {
tokenRegistry.push(response.tokenInfo);
}
}
return {
returnCode: 36864 /* LedgerError.NoErrors */,
errorMessage: "No errors",
tokenRegistry: tokenRegistry,
};
}
}
exports.default = InternetComputerApp;
//# sourceMappingURL=index.js.map