@zondax/ledger-polkadot
Version:
TS / Node API for Polkadot apps running on Ledger Nano S/S+/X and Stax
233 lines (232 loc) • 9.66 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.PolkadotApp = void 0;
const utils_1 = require("./utils");
class PolkadotApp {
transport;
cla = 0x90;
constructor(transport) {
if (transport == null)
throw new Error("Transport has not been defined");
this.transport = transport;
}
getChunks(path, blob, metadata) {
if (blob.length >= 2 ** 16)
throw new Error("Blob length should be smaller than 2^16 bytes");
const chunks = [];
const bip44Path = (0, utils_1.serializePath)(path);
const blobLen = Buffer.alloc(2);
blobLen.writeUInt16LE(blob.length);
const firstChunk = Buffer.concat([bip44Path, blobLen]);
chunks.push(firstChunk);
if (metadata == null)
chunks.push(...(0, utils_1.splitChunks)(blob));
else
chunks.push(...(0, utils_1.splitChunks)(Buffer.concat([blob, metadata])));
return chunks;
}
async getVersion() {
return await this.transport.send(this.cla, 0 /* INS.GET_VERSION */, 0, 0).then((response) => {
const errorCodeData = response.subarray(-2);
const returnCode = errorCodeData[0] * 256 + errorCodeData[1];
const retObj = {
returnCode,
errorMessage: (0, utils_1.errorCodeToString)(returnCode),
locked: false,
testMode: false,
major: 0,
minor: 0,
patch: 0,
targetId: 0,
};
// 12 bytes + 2 error code
// if it's not, data is invalid
if (response.length !== 14) {
retObj.returnCode = 27012 /* ERROR_CODE.InvalidData */;
retObj.errorMessage = (0, utils_1.errorCodeToString)(27012 /* ERROR_CODE.InvalidData */);
return retObj;
}
retObj.testMode = response[0] !== 0;
retObj.major = response[1] * 256 + response[2];
retObj.minor = response[3] * 256 + response[4];
retObj.patch = response[5] * 256 + response[6];
retObj.locked = response[7] === 1;
retObj.targetId = (response[8] << 24) + (response[9] << 16) + (response[10] << 8) + (response[11] << 0);
return retObj;
}, (err) => {
const { returnCode, errorMessage } = (0, utils_1.processErrorResponse)(err);
return {
returnCode,
errorMessage,
locked: false,
testMode: false,
major: 0,
minor: 0,
patch: 0,
targetId: 0,
};
});
}
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];
if (response[0] !== 1) {
// Ledger responds with format ID 1. There is no spec for any format != 1
return {
returnCode: 0x9001,
errorMessage: "Response format ID not recognized",
appName: "err",
appVersion: "err",
flagsValue: 0,
flags: {
recovery: false,
signedMcuCode: false,
onboarded: false,
issuerTrusted: false,
customCaTrusted: false,
hsmInitialized: false,
factoryFilled: false,
pinValidated: false,
},
};
}
const appNameLen = response[1];
const appName = response.subarray(2, 2 + appNameLen).toString("ascii");
let idx = 2 + appNameLen;
const appVersionLen = response[idx];
idx += 1;
const appVersion = response.subarray(idx, idx + appVersionLen).toString("ascii");
idx += appVersionLen;
idx += 1;
const flagsValue = response[idx];
return {
returnCode,
errorMessage: (0, utils_1.errorCodeToString)(returnCode),
appName: appName ?? "err",
appVersion: appVersion ?? "err",
flagsValue,
flags: {
recovery: (flagsValue & 0b1) === 1,
signedMcuCode: (flagsValue & 0b10) === 1,
onboarded: (flagsValue & 0b100) === 1,
issuerTrusted: (flagsValue & 0b1000) === 1,
customCaTrusted: (flagsValue & 0b10000) === 1,
hsmInitialized: (flagsValue & 0b100000) === 1,
factoryFilled: (flagsValue & 0b1000000) === 1,
pinValidated: (flagsValue & 0b10000000) === 1,
},
};
}, (err) => {
const { returnCode, errorMessage } = (0, utils_1.processErrorResponse)(err);
return {
returnCode,
errorMessage,
appName: "err",
appVersion: "err",
flagsValue: 0,
flags: {
recovery: false,
signedMcuCode: false,
onboarded: false,
issuerTrusted: false,
customCaTrusted: false,
hsmInitialized: false,
factoryFilled: false,
pinValidated: false,
},
};
});
}
async getAddress(path, ss58prefix, showAddrInDevice = false) {
// needs to be integer, and up to 65535
if (!Number.isInteger(ss58prefix) && ss58prefix >> 16 !== 0) {
throw new Error(`Unexpected ss58prefix ${ss58prefix}. Needs to be up to 2^16`);
}
const bip44Path = (0, utils_1.serializePath)(path);
const payload = Buffer.alloc(bip44Path.length + 2);
bip44Path.copy(payload);
payload.writeUInt16LE(ss58prefix, payload.length - 2);
const p1 = showAddrInDevice ? 1 /* P1_VALUES.SHOW_ADDR */ : 0 /* P1_VALUES.GET_ADDR */;
return await this.transport.send(this.cla, 1 /* INS.GET_ADDR */, p1, 0, payload).then((response) => {
const errorCodeData = response.subarray(-2);
const errorCode = errorCodeData[0] * 256 + errorCodeData[1];
return {
pubKey: response.subarray(0, 32),
address: response.subarray(32, response.length - 2).toString("ascii"),
returnCode: errorCode,
errorMessage: (0, utils_1.errorCodeToString)(errorCode),
};
}, (err) => {
const { returnCode, errorMessage } = (0, utils_1.processErrorResponse)(err);
return {
returnCode,
errorMessage,
pubKey: Buffer.alloc(0),
address: "",
};
});
}
async signSendChunk(chunkIdx, chunkNum, chunk, 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 */;
}
return await this.transport
.send(this.cla, ins, payloadType, 0, chunk, [36864 /* ERROR_CODE.NoError */, 27012 /* ERROR_CODE.InvalidData */])
.then((response) => {
const errorCodeData = response.subarray(-2);
const returnCode = errorCodeData[0] * 256 + errorCodeData[1];
let errorMessage = (0, utils_1.errorCodeToString)(returnCode);
let signature = Buffer.alloc(0);
if (response.length > 2) {
if (returnCode === 0x6984) {
errorMessage = response.subarray(0, response.length - 2).toString("ascii");
}
else {
signature = response.subarray(0, response.length - 2);
}
}
return {
signature,
returnCode,
errorMessage,
};
}, (err) => {
const { returnCode, errorMessage } = (0, utils_1.processErrorResponse)(err);
return {
returnCode,
errorMessage,
signature: Buffer.alloc(0),
};
});
}
async signImpl(path, ins, blob, metadata) {
const chunks = this.getChunks(path, blob, metadata);
return await this.signSendChunk(1, chunks.length, chunks[0], ins).then(async (result) => {
for (let i = 1; i < chunks.length; i += 1) {
result = await this.signSendChunk(1 + i, chunks.length, chunks[i], ins);
if (result.returnCode !== 36864 /* ERROR_CODE.NoError */)
break;
}
return result;
}, (err) => {
const { returnCode, errorMessage } = (0, utils_1.processErrorResponse)(err);
return {
returnCode,
errorMessage,
signature: Buffer.alloc(0),
};
});
}
async sign(path, blob, metadata) {
return await this.signImpl(path, 2 /* INS.SIGN */, blob, metadata);
}
async signRaw(path, blob) {
return await this.signImpl(path, 3 /* INS.SIGN_RAW */, blob);
}
}
exports.PolkadotApp = PolkadotApp;