@ledgerhq/hw-app-cosmos
Version:
Ledger Hardware Wallet Cosmos Application API
139 lines • 5.25 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const bip32_path_1 = __importDefault(require("bip32-path"));
const errors_1 = require("@ledgerhq/errors");
const CHUNK_SIZE = 250;
const CLA = 0x55;
const APP_KEY = "CSM";
const INS_GET_VERSION = 0x00;
const INS_SIGN_SECP256K1 = 0x02;
const INS_GET_ADDR_SECP256K1 = 0x04;
const PAYLOAD_TYPE_INIT = 0x00;
const PAYLOAD_TYPE_ADD = 0x01;
const PAYLOAD_TYPE_LAST = 0x02;
const SW_OK = 0x9000;
const SW_CANCEL = 0x6986;
/**
* Cosmos API
*
* @example
* import Cosmos from "@ledgerhq/hw-app-cosmos";
* const cosmos = new Cosmos(transport)
*/
class Cosmos {
transport;
constructor(transport, scrambleKey = APP_KEY) {
this.transport = transport;
transport.decorateAppAPIMethods(this, ["getAddress", "sign", "getAppConfiguration"], scrambleKey);
}
// FIXME: understand what is going on with the return type here
getAppConfiguration() {
return this.transport.send(CLA, INS_GET_VERSION, 0, 0).then(response => {
return {
test_mode: response[0] !== 0,
version: "" + response[1] + "." + response[2] + "." + response[3],
device_locked: response[4] === 1,
major: response[1],
};
});
}
serializePath(path) {
const buf = Buffer.alloc(20);
// HACK : without the >>>,
// the bitwise implicitly casts the result to be a signed int32,
// which fails the internal type check of Buffer in case of overload.
buf.writeUInt32LE((0x80000000 | path[0]) >>> 0, 0);
buf.writeUInt32LE((0x80000000 | path[1]) >>> 0, 4);
buf.writeUInt32LE((0x80000000 | path[2]) >>> 0, 8);
buf.writeUInt32LE(path[3], 12);
buf.writeUInt32LE(path[4], 16);
return buf;
}
serializeHRP(hrp) {
if (hrp == null || hrp.length === 0 || hrp.length > 83) {
throw new Error("Invalid HRP");
}
const buf = Buffer.alloc(1 + hrp.length);
buf.writeUInt8(hrp.length, 0);
buf.write(hrp, 1);
return buf;
}
/**
* get Cosmos address for a given BIP 32 path.
* @param path a path in BIP 32 format
* @param hrp usually cosmos
* @option boolDisplay optionally enable or not the display
* @return an object with a publicKey, address and (optionally) chainCode
* @example
* cosmos.getAddress("44'/60'/0'/0/0", "cosmos").then(o => o.address)
*/
getAddress(path, hrp, boolDisplay) {
const bipPath = bip32_path_1.default.fromString(path).toPathArray();
const serializedPath = this.serializePath(bipPath);
const data = Buffer.concat([this.serializeHRP(hrp), serializedPath]);
return this.transport
.send(CLA, INS_GET_ADDR_SECP256K1, boolDisplay ? 1 : 0, 0, data, [SW_OK])
.then(response => {
const address = Buffer.from(response.slice(33, -2)).toString();
const publicKey = Buffer.from(response.slice(0, 33)).toString("hex");
return {
address,
publicKey,
};
});
}
foreach(arr, callback) {
function iterate(index, array, result) {
if (index >= array.length) {
return result;
}
else
return callback(array[index], index).then(function (res) {
result.push(res);
return iterate(index + 1, array, result);
});
}
return Promise.resolve().then(() => iterate(0, arr, []));
}
async sign(path, message) {
const bipPath = bip32_path_1.default.fromString(path).toPathArray();
const serializedPath = this.serializePath(bipPath);
const chunks = [];
chunks.push(serializedPath);
const buffer = Buffer.from(message);
for (let i = 0; i < buffer.length; i += CHUNK_SIZE) {
let end = i + CHUNK_SIZE;
if (i > buffer.length) {
end = buffer.length;
}
chunks.push(buffer.slice(i, end));
}
let response = {};
return this.foreach(chunks, (data, j) => this.transport
.send(CLA, INS_SIGN_SECP256K1, j === 0
? PAYLOAD_TYPE_INIT
: j + 1 === chunks.length
? PAYLOAD_TYPE_LAST
: PAYLOAD_TYPE_ADD, 0, data, [SW_OK, SW_CANCEL])
.then(apduResponse => (response = apduResponse))).then(() => {
const errorCodeData = response.slice(-2);
const returnCode = errorCodeData[0] * 256 + errorCodeData[1];
let signature = null;
if (response.length > 2) {
signature = response.slice(0, response.length - 2);
}
if (returnCode === 0x6986) {
throw new errors_1.UserRefusedOnDevice();
}
return {
signature,
return_code: returnCode,
};
});
}
}
exports.default = Cosmos;
//# sourceMappingURL=Cosmos.js.map