@zondax/ledger-cosmos-js
Version:
Node API for Cosmos App (Ledger Nano S/S+/X)
202 lines • 9.93 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 });
const ripemd160_1 = require("@noble/hashes/ripemd160");
const sha256_1 = require("@noble/hashes/sha256");
const base_1 = require("@scure/base");
const ledger_js_1 = __importStar(require("@zondax/ledger-js"));
const byteStream_1 = require("@zondax/ledger-js/dist/byteStream");
const consts_1 = require("./consts");
var CosmosCostumError;
(function (CosmosCostumError) {
CosmosCostumError[CosmosCostumError["ErrorTransactionDataExceedsBufferCapacity"] = 27016] = "ErrorTransactionDataExceedsBufferCapacity";
CosmosCostumError[CosmosCostumError["ErrorInvalidHDPathValue"] = 27017] = "ErrorInvalidHDPathValue";
CosmosCostumError[CosmosCostumError["ErrorHrpWrongLength"] = 27018] = "ErrorHrpWrongLength";
CosmosCostumError[CosmosCostumError["ErrorInvalidHDPathCoinValue"] = 27019] = "ErrorInvalidHDPathCoinValue";
CosmosCostumError[CosmosCostumError["ErrorChainConfigNotSupported"] = 27020] = "ErrorChainConfigNotSupported";
})(CosmosCostumError || (CosmosCostumError = {}));
const COSMOS_CUSTOM_ERROR_DESCRIPTIONS = {
...ledger_js_1.ERROR_DESCRIPTION_OVERRIDE,
[CosmosCostumError.ErrorTransactionDataExceedsBufferCapacity]: "Transaction data exceeds the device's internal buffer capacity",
[CosmosCostumError.ErrorInvalidHDPathValue]: 'Invalid HD Path Value. Expert Mode required.',
[CosmosCostumError.ErrorHrpWrongLength]: 'Invalid HRP length',
[CosmosCostumError.ErrorInvalidHDPathCoinValue]: 'Invalid HD Path Coin Value',
[CosmosCostumError.ErrorChainConfigNotSupported]: 'Chain config not supported',
};
class CosmosApp extends ledger_js_1.default {
/**
* Constructs a new CosmosApp instance.
* @param transport - The transport instance.
* @throws {Error} - If the transport is not defined.
*/
constructor(transport) {
super(transport, CosmosApp._params);
if (!this.transport) {
throw new Error('Transport has not been defined');
}
}
serializeHRP(hrp) {
if (!hrp || hrp.length < 3 || hrp.length > 83) {
throw new Error('Invalid HRP');
}
const buf = new byteStream_1.ByteStream();
buf.appendUint8(hrp.length);
buf.appendBytes(Buffer.from(hrp));
return buf.getCompleteBuffer();
}
serializeCosmosPath(path) {
if (typeof path !== 'string') {
throw new ledger_js_1.ResponseError(ledger_js_1.LedgerError.GenericError, "Path should be a string (e.g \"m/44'/461'/5'/0/3\")");
}
if (!path.startsWith('m/')) {
throw new ledger_js_1.ResponseError(ledger_js_1.LedgerError.GenericError, 'Path should start with "m/" (e.g "m/44\'/461\'/5\'/0/3")');
}
const pathArray = path.split('/').slice(1);
if (this.REQUIRED_PATH_LENGTHS && this.REQUIRED_PATH_LENGTHS.length > 0 && !this.REQUIRED_PATH_LENGTHS.includes(pathArray.length)) {
throw new ledger_js_1.ResponseError(ledger_js_1.LedgerError.GenericError, "Invalid path length. (e.g \"m/44'/5757'/5'/0/3\")");
}
const buf = new byteStream_1.ByteStream();
pathArray.forEach((child) => {
let value = 0;
if (child.endsWith("'")) {
value += ledger_js_1.HARDENED;
child = child.slice(0, -1);
}
const numChild = Number(child);
if (Number.isNaN(numChild)) {
throw new ledger_js_1.ResponseError(ledger_js_1.LedgerError.GenericError, `Invalid path : ${child} is not a number. (e.g "m/44'/461'/5'/0/3")`);
}
value += numChild;
buf.appendUint32(value);
});
return buf.getCompleteBuffer();
}
async publicKey(path) {
const serializedPath = this.serializeCosmosPath(path);
const data = Buffer.concat([this.serializeHRP('cosmos'), serializedPath]);
try {
const responseBuffer = await this.transport.send(this.CLA, this.INS.GET_ADDR_SECP256K1, 0, 0, data);
const response = (0, ledger_js_1.processResponse)(responseBuffer);
const compressed_pk = Buffer.from(response.readBytes(consts_1.PKLEN));
const bech32_address = response.readBytes(response.length()).toString();
return { compressed_pk, bech32_address };
}
catch (e) {
throw (0, ledger_js_1.processErrorResponse)(e, COSMOS_CUSTOM_ERROR_DESCRIPTIONS);
}
}
async getAddressAndPubKey(path, hrp) {
const serializedPath = this.serializeCosmosPath(path);
const data = Buffer.concat([this.serializeHRP(hrp), serializedPath]);
try {
const responseBuffer = await this.transport.send(this.CLA, this.INS.GET_ADDR_SECP256K1, 0 /* P1_VALUES.ONLY_RETRIEVE */, 0, data);
const response = (0, ledger_js_1.processResponse)(responseBuffer);
const compressed_pk = response.readBytes(consts_1.PKLEN);
const bech32_address = response.readBytes(response.length()).toString();
return { compressed_pk, bech32_address };
}
catch (e) {
throw (0, ledger_js_1.processErrorResponse)(e, COSMOS_CUSTOM_ERROR_DESCRIPTIONS);
}
}
async showAddressAndPubKey(path, hrp) {
const serializedPath = this.serializeCosmosPath(path);
const data = Buffer.concat([this.serializeHRP(hrp), serializedPath]);
try {
const responseBuffer = await this.transport.send(this.CLA, this.INS.GET_ADDR_SECP256K1, 1 /* P1_VALUES.SHOW_ADDRESS_IN_DEVICE */, 0, data);
const response = (0, ledger_js_1.processResponse)(responseBuffer);
const compressed_pk = response.readBytes(consts_1.PKLEN);
const bech32_address = response.readBytes(response.length()).toString();
return { compressed_pk, bech32_address };
}
catch (e) {
throw (0, ledger_js_1.processErrorResponse)(e, COSMOS_CUSTOM_ERROR_DESCRIPTIONS);
}
}
static getBech32FromPK(hrp, pk) {
if (pk.length !== 33) {
throw new Error('expected compressed public key [31 bytes]');
}
const hashSha256 = (0, sha256_1.sha256)(pk);
const hashRip = (0, ripemd160_1.ripemd160)(hashSha256);
return base_1.bech32.encode(hrp, base_1.bech32.toWords(hashRip));
}
async prepareChunks_hrp(path, buffer, hrp) {
const serializedPath = this.serializeCosmosPath(path);
const firstChunk = hrp ? Buffer.concat([serializedPath, this.serializeHRP(hrp)]) : serializedPath;
const chunks = [firstChunk];
for (let i = 0; i < buffer.length; i += this.CHUNK_SIZE) {
const end = Math.min(i + this.CHUNK_SIZE, buffer.length);
chunks.push(buffer.subarray(i, end));
}
return chunks;
}
async signSendCosmosChunk(chunkIdx, chunkNum, chunk, txtype = 0 /* P2_VALUES.JSON */) {
const payloadType = chunkIdx === 1 ? ledger_js_1.PAYLOAD_TYPE.INIT : chunkIdx === chunkNum ? ledger_js_1.PAYLOAD_TYPE.LAST : ledger_js_1.PAYLOAD_TYPE.ADD;
const statusList = [ledger_js_1.LedgerError.NoErrors, ledger_js_1.LedgerError.DataIsInvalid, ledger_js_1.LedgerError.BadKeyHandle];
const responseBuffer = await this.transport.send(this.CLA, this.INS.SIGN_SECP256K1, payloadType, txtype, chunk, statusList);
return (0, ledger_js_1.processResponse)(responseBuffer);
}
async signImpl(path, buffer, txtype, hrp) {
const chunks = await this.prepareChunks_hrp(path, buffer, hrp);
try {
let result = await this.signSendCosmosChunk(1, chunks.length, chunks[0], txtype);
for (let i = 1; i < chunks.length; i++) {
result = await this.signSendCosmosChunk(i + 1, chunks.length, chunks[i], txtype);
}
return { signature: result.readBytes(result.length()) };
}
catch (e) {
throw (0, ledger_js_1.processErrorResponse)(e, COSMOS_CUSTOM_ERROR_DESCRIPTIONS);
}
}
async sign(path, buffer, hrp, txtype = 0 /* P2_VALUES.JSON */) {
return this.signImpl(path, buffer, txtype, hrp);
}
}
CosmosApp._INS = {
GET_VERSION: 0x00,
SIGN_SECP256K1: 0x02,
GET_ADDR_SECP256K1: 0x04,
};
CosmosApp._params = {
cla: consts_1.CLA,
ins: { ...CosmosApp._INS },
p1Values: { ONLY_RETRIEVE: 0x00, SHOW_ADDRESS_IN_DEVICE: 0x01 },
chunkSize: 250,
requiredPathLengths: [5],
};
exports.default = CosmosApp;
//# sourceMappingURL=index.js.map