@zondax/ledger-substrate
Version:
TS / Node API for Substrate/Polkadot based apps running on Ledger devices
383 lines • 15.4 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.SubstrateApp = void 0;
const common_1 = require("./common");
/**
* Class representing a Substrate application.
*/
class SubstrateApp {
/**
* Create a SubstrateApp instance.
* @param transport - The transport instance.
* @param cla - The CLA value.
* @param slip0044 - The SLIP-0044 value.
*/
constructor(transport, cla, slip0044) {
if (transport == null) {
throw new Error('Transport has not been defined');
}
this.transport = transport;
this.cla = cla;
this.slip0044 = slip0044;
}
/**
* Serialize the BIP44 path.
* @param slip0044 - The SLIP-0044 value.
* @param account - The account index.
* @param change - The change index.
* @param addressIndex - The address index.
* @returns The serialized path.
*/
static serializePath(slip0044, account, change, addressIndex) {
if (!Number.isInteger(account))
throw new Error('Input must be an integer');
if (!Number.isInteger(change))
throw new Error('Input must be an integer');
if (!Number.isInteger(addressIndex))
throw new Error('Input must be an integer');
const buf = Buffer.alloc(20);
buf.writeUInt32LE(0x8000002c, 0);
buf.writeUInt32LE(slip0044, 4);
buf.writeUInt32LE(account, 8);
buf.writeUInt32LE(change, 12);
buf.writeUInt32LE(addressIndex, 16);
return buf;
}
/**
* Split a message into chunks.
* @param message - The message to split.
* @returns The message chunks.
*/
static GetChunks(message) {
const chunks = [];
const buffer = Buffer.from(message);
for (let i = 0; i < buffer.length; i += common_1.CHUNK_SIZE) {
let end = i + common_1.CHUNK_SIZE;
if (i > buffer.length) {
end = buffer.length;
}
chunks.push(buffer.subarray(i, end));
}
return chunks;
}
/**
* Get chunks for signing.
* @param slip0044 - The SLIP-0044 value.
* @param account - The account index.
* @param change - The change index.
* @param addressIndex - The address index.
* @param message - The message to sign.
* @returns The chunks for signing.
*/
static signGetChunks(slip0044, account, change, addressIndex, message) {
const chunks = [];
const bip44Path = SubstrateApp.serializePath(slip0044, account, change, addressIndex);
chunks.push(bip44Path);
chunks.push(...SubstrateApp.GetChunks(message));
return chunks;
}
/**
* Get the version of the application.
* @returns The version response.
*/
async getVersion() {
try {
return await (0, common_1.getVersion)(this.transport, this.cla);
}
catch (e) {
return (0, common_1.processErrorResponse)(e);
}
}
/**
* Get application information.
* @returns The application information.
*/
async appInfo() {
return await this.transport.send(0xb0, 0x01, 0, 0).then(response => {
const errorCodeData = response.subarray(-2);
const returnCode = errorCodeData[0] * 256 + errorCodeData[1];
let appName = '';
let appVersion = '';
let flagLen = 0;
let flagsValue = 0;
if (response[0] !== 1) {
// Ledger responds with format ID 1. There is no spec for any format != 1
return {
return_code: 0x9001,
error_message: 'response format ID not recognized',
};
}
else {
const appNameLen = response[1];
appName = response.subarray(2, 2 + appNameLen).toString('ascii');
let idx = 2 + appNameLen;
const appVersionLen = response[idx];
idx += 1;
appVersion = response.subarray(idx, idx + appVersionLen).toString('ascii');
idx += appVersionLen;
const appFlagsLen = response[idx];
idx += 1;
flagLen = appFlagsLen;
flagsValue = response[idx];
}
return {
return_code: returnCode,
error_message: (0, common_1.errorCodeToString)(returnCode),
appName: appName === '' || 'err',
appVersion: appVersion === '' || 'err',
flagLen,
flagsValue,
flag_recovery: (flagsValue & 1) !== 0,
flag_signed_mcu_code: (flagsValue & 2) !== 0,
flag_onboarded: (flagsValue & 4) !== 0,
flag_pin_validated: (flagsValue & 128) !== 0,
};
}, common_1.processErrorResponse);
}
/**
* Get the address.
* @param account - The account index.
* @param change - The change index.
* @param addressIndex - The address index.
* @param [requireConfirmation=false] - Whether confirmation is required.
* @param [scheme=SCHEME.ED25519] - The scheme.
* @returns The address response.
*/
async getAddress(account, change, addressIndex, requireConfirmation = false, scheme = 0 /* SCHEME.ED25519 */) {
const bip44Path = SubstrateApp.serializePath(this.slip0044, account, change, addressIndex);
let p1 = 0;
if (requireConfirmation)
p1 = 1;
let p2 = 0;
if (!isNaN(scheme))
p2 = scheme;
return await this.transport.send(this.cla, 1 /* INS.GET_ADDR */, p1, p2, bip44Path).then(response => {
const errorCodeData = response.subarray(-2);
const errorCode = errorCodeData[0] * 256 + errorCodeData[1];
let pubkeyLen = 32;
if (scheme == 2 /* SCHEME.ECDSA */) {
pubkeyLen = 33;
}
return {
pubKey: response.subarray(0, pubkeyLen).toString('hex'),
address: response.subarray(pubkeyLen, response.length - 2).toString('ascii'),
return_code: errorCode,
error_message: (0, common_1.errorCodeToString)(errorCode),
};
}, common_1.processErrorResponse);
}
/**
* Send a chunk for signing.
* @private
* @param chunkIdx - The chunk index.
* @param chunkNum - The total number of chunks.
* @param chunk - The chunk data.
* @param [scheme=SCHEME.ED25519] - The scheme.
* @param [ins=INS.SIGN] - The instruction.
* @returns The response.
*/
async signSendChunk(chunkIdx, chunkNum, chunk, scheme = 0 /* SCHEME.ED25519 */, ins = 2 /* INS.SIGN */) {
let payloadType = 1 /* PAYLOAD_TYPE.ADD */;
if (chunkIdx === 1) {
payloadType = 0 /* PAYLOAD_TYPE.INIT */;
}
if (chunkIdx === chunkNum) {
payloadType = 2 /* PAYLOAD_TYPE.LAST */;
}
let p2 = 0;
if (!isNaN(scheme))
p2 = scheme;
return await this.transport.send(this.cla, ins, payloadType, p2, chunk, [36864 /* ERROR_CODE.NoError */, 0x6984, 0x6a80]).then(response => {
const errorCodeData = response.subarray(-2);
const returnCode = errorCodeData[0] * 256 + errorCodeData[1];
let errorMessage = (0, common_1.errorCodeToString)(returnCode);
let signature = null;
if (returnCode === 0x6a80 || returnCode === 0x6984) {
errorMessage = response.subarray(0, response.length - 2).toString('ascii');
}
else if (response.length > 2) {
signature = response.subarray(0, response.length - 2);
}
return {
signature,
return_code: returnCode,
error_message: errorMessage,
};
}, common_1.processErrorResponse);
}
/**
* Implementation of the signing process.
* @private
* @param account - The account index.
* @param change - The change index.
* @param addressIndex - The address index.
* @param message - The message to sign.
* @param ins - The instruction.
* @param [scheme=SCHEME.ED25519] - The scheme.
* @returns The signing response.
*/
async signImpl(account, change, addressIndex, message, ins, scheme = 0 /* SCHEME.ED25519 */) {
const chunks = SubstrateApp.signGetChunks(this.slip0044, account, change, addressIndex, message);
return await this.signSendChunk(1, chunks.length, chunks[0], scheme, ins).then(async () => {
let result;
for (let i = 1; i < chunks.length; i += 1) {
result = await this.signSendChunk(1 + i, chunks.length, chunks[i], scheme, ins);
if (result.return_code !== 36864 /* ERROR_CODE.NoError */) {
break;
}
}
return {
return_code: result.return_code,
error_message: result.error_message,
signature: result.signature,
};
}, common_1.processErrorResponse);
}
/**
* Sign a message.
* @param account - The account index.
* @param change - The change index.
* @param addressIndex - The address index.
* @param message - The message to sign.
* @param [scheme=SCHEME.ED25519] - The scheme.
* @returns The signing response.
*/
async sign(account, change, addressIndex, message, scheme = 0 /* SCHEME.ED25519 */) {
return await this.signImpl(account, change, addressIndex, message, 2 /* INS.SIGN */, scheme);
}
/**
* Sign a raw message.
* @param account - The account index.
* @param change - The change index.
* @param addressIndex - The address index.
* @param message - The message to sign.
* @param [scheme=SCHEME.ED25519] - The scheme.
* @returns The signing response.
*/
async signRaw(account, change, addressIndex, message, scheme = 0 /* SCHEME.ED25519 */) {
return await this.signImpl(account, change, addressIndex, message, 3 /* INS.SIGN_RAW */, scheme);
}
/**
* @deprecated This function is deprecated and will be removed in future versions.
* Get the allowlist public key.
* @returns The allowlist public key response.
*/
async getAllowlistPubKey() {
return await this.transport.send(this.cla, 144 /* INS.ALLOWLIST_GET_PUBKEY */, 0, 0).then(response => {
const errorCodeData = response.subarray(-2);
const returnCode = errorCodeData[0] * 256 + errorCodeData[1];
console.log(response);
const pubkey = response.subarray(0, 32);
// 32 bytes + 2 error code
if (response.length !== 34) {
return {
return_code: 0x6984,
error_message: (0, common_1.errorCodeToString)(0x6984),
};
}
return {
return_code: returnCode,
error_message: (0, common_1.errorCodeToString)(returnCode),
pubkey,
};
}, common_1.processErrorResponse);
}
/**
* @deprecated This function is deprecated and will be removed in future versions.
* Set the allowlist public key.
* @param pk - The public key.
* @returns The response.
*/
async setAllowlistPubKey(pk) {
return await this.transport.send(this.cla, 145 /* INS.ALLOWLIST_SET_PUBKEY */, 0, 0, pk).then(response => {
const errorCodeData = response.subarray(-2);
const returnCode = errorCodeData[0] * 256 + errorCodeData[1];
return {
return_code: returnCode,
error_message: (0, common_1.errorCodeToString)(returnCode),
};
}, common_1.processErrorResponse);
}
/**
* @deprecated This function is deprecated and will be removed in future versions.
* Get the allowlist hash.
* @returns The allowlist hash response.
*/
async getAllowlistHash() {
return await this.transport.send(this.cla, 146 /* INS.ALLOWLIST_GET_HASH */, 0, 0).then(response => {
const errorCodeData = response.subarray(-2);
const returnCode = errorCodeData[0] * 256 + errorCodeData[1];
console.log(response);
const hash = response.subarray(0, 32);
// 32 bytes + 2 error code
if (response.length !== 34) {
return {
return_code: 0x6984,
error_message: (0, common_1.errorCodeToString)(0x6984),
};
}
return {
return_code: returnCode,
error_message: (0, common_1.errorCodeToString)(returnCode),
hash,
};
}, common_1.processErrorResponse);
}
/**
* @deprecated This function is deprecated and will be removed in future versions.
* Send a chunk for uploading the allowlist.
* @param chunkIdx - The chunk index.
* @param chunkNum - The total number of chunks.
* @param chunk - The chunk data.
* @returns The response.
*/
async uploadSendChunk(chunkIdx, chunkNum, chunk) {
let payloadType = 1 /* PAYLOAD_TYPE.ADD */;
if (chunkIdx === 1) {
payloadType = 0 /* PAYLOAD_TYPE.INIT */;
}
if (chunkIdx === chunkNum) {
payloadType = 2 /* PAYLOAD_TYPE.LAST */;
}
return await this.transport.send(this.cla, 147 /* INS.ALLOWLIST_UPLOAD */, payloadType, 0, chunk, [36864 /* ERROR_CODE.NoError */]).then(response => {
const errorCodeData = response.subarray(-2);
const returnCode = errorCodeData[0] * 256 + errorCodeData[1];
const errorMessage = (0, common_1.errorCodeToString)(returnCode);
return {
return_code: returnCode,
error_message: errorMessage,
};
}, common_1.processErrorResponse);
}
/**
* @deprecated This function is deprecated and will be removed in future versions.
* Upload the allowlist.
* @param message - The allowlist message.
* @returns The response.
*/
async uploadAllowlist(message) {
const chunks = [];
chunks.push(Buffer.from([0]));
chunks.push(...SubstrateApp.GetChunks(message));
return await this.uploadSendChunk(1, chunks.length, chunks[0]).then(async (result) => {
if (result.return_code !== 36864 /* ERROR_CODE.NoError */) {
return {
return_code: result.return_code,
error_message: result.error_message,
};
}
for (let i = 1; i < chunks.length; i += 1) {
result = await this.uploadSendChunk(1 + i, chunks.length, chunks[i]);
if (result.return_code !== 36864 /* ERROR_CODE.NoError */) {
break;
}
}
return {
return_code: result.return_code,
error_message: result.error_message,
};
}, common_1.processErrorResponse);
}
}
exports.SubstrateApp = SubstrateApp;
//# sourceMappingURL=substrate_app.js.map