@thorchain/ledger-thorchain
Version:
Node API for THORChain App (Ledger Nano S/X)
264 lines • 13.1 kB
JavaScript
;
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __generator = (this && this.__generator) || function (thisArg, body) {
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
function verb(n) { return function (v) { return step([n, v]); }; }
function step(op) {
if (f) throw new TypeError("Generator is already executing.");
while (_) try {
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
if (y = 0, t) op = [op[0] & 2, t.value];
switch (op[0]) {
case 0: case 1: t = op; break;
case 4: _.label++; return { value: op[1], done: false };
case 5: _.label++; y = op[1]; op = [0]; continue;
case 7: op = _.ops.pop(); _.trys.pop(); continue;
default:
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
if (t[2]) _.ops.pop();
_.trys.pop(); continue;
}
op = body.call(thisArg, _);
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
}
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.extractSignatureFromTLV = exports.getPublicKey = exports.getVersion = exports.signSendChunk = exports.serializePath = exports.getBech32FromPK = exports.serializeHRP = void 0;
var common_1 = require("./common");
var crypto_1 = __importDefault(require("crypto"));
var ripemd160_1 = __importDefault(require("ripemd160"));
var bech32_1 = require("bech32");
function serializeHRP(hrp) {
if (!hrp || hrp.length < 3 || hrp.length > 83) {
throw new common_1.LedgerError(common_1.LedgerErrorType.HPRInvalid);
}
var buf = Buffer.alloc(1 + hrp.length);
buf.writeUInt8(hrp.length, 0);
buf.write(hrp, 1);
return buf;
}
exports.serializeHRP = serializeHRP;
function getBech32FromPK(hrp, pk) {
if (pk.length !== 33) {
throw new common_1.LedgerError(common_1.LedgerErrorType.PKInvalidBytes);
}
var hashSha256 = crypto_1.default.createHash("sha256").update(pk).digest();
var hashRip = new ripemd160_1.default().update(hashSha256).digest();
var words = bech32_1.bech32.toWords(hashRip);
return bech32_1.bech32.encode(hrp, words);
}
exports.getBech32FromPK = getBech32FromPK;
function serializePath(path) {
if (!path || path.length !== 5) {
throw new Error("Invalid path.");
}
var buf = Buffer.alloc(20);
buf.writeUInt32LE(0x80000000 + path[0], 0);
buf.writeUInt32LE(0x80000000 + path[1], 4);
buf.writeUInt32LE(0x80000000 + path[2], 8);
buf.writeUInt32LE(path[3], 12);
buf.writeUInt32LE(path[4], 16);
return buf;
}
exports.serializePath = serializePath;
function signSendChunk(transport, chunkIdx, chunkNum, chunk) {
return __awaiter(this, void 0, void 0, function () {
var payloadType, response, errorCodeData, returnCode, errorMessage;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
payloadType = common_1.PAYLOAD_TYPE.ADD;
if (chunkIdx === 1) {
payloadType = common_1.PAYLOAD_TYPE.INIT;
}
if (chunkIdx === chunkNum) {
payloadType = common_1.PAYLOAD_TYPE.LAST;
}
return [4 /*yield*/, transport.send(common_1.CLA, common_1.INS.SIGN_SECP256K1, payloadType, 0, chunk, [
common_1.LedgerErrorType.NoErrors,
common_1.LedgerErrorType.DataIsInvalid,
common_1.LedgerErrorType.BadKeyHandle,
common_1.LedgerErrorType.SignVerifyError,
])];
case 1:
response = _a.sent();
errorCodeData = response.slice(-2);
returnCode = errorCodeData[0] * 256 + errorCodeData[1];
errorMessage = common_1.errorCodeToString(returnCode);
if (returnCode === common_1.LedgerErrorType.BadKeyHandle ||
returnCode === common_1.LedgerErrorType.DataIsInvalid ||
returnCode === common_1.LedgerErrorType.SignVerifyError) {
errorMessage = errorMessage + " : " + response.slice(0, response.length - 2).toString("ascii");
throw new common_1.LedgerError(returnCode, errorMessage);
}
if (returnCode === common_1.LedgerErrorType.NoErrors && response.length > 2) {
return [2 /*return*/, {
returnCode: returnCode,
errorMessage: errorMessage,
signature: response.slice(0, response.length - 2),
}];
}
return [2 /*return*/, {
returnCode: returnCode,
errorMessage: errorMessage,
}];
}
});
});
}
exports.signSendChunk = signSendChunk;
function getVersion(transport) {
return __awaiter(this, void 0, void 0, function () {
var response, errorCodeData, returnCode, targetId, error_1;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
_a.trys.push([0, 2, , 3]);
return [4 /*yield*/, transport.send(common_1.CLA, common_1.INS.GET_VERSION, 0, 0)];
case 1:
response = _a.sent();
errorCodeData = response.slice(-2);
returnCode = (errorCodeData[0] * 256 + errorCodeData[1]);
targetId = 0;
if (response.length >= 9) {
/* eslint-disable no-bitwise */
targetId = (response[5] << 24) + (response[6] << 16) + (response[7] << 8) + (response[8] << 0);
/* eslint-enable no-bitwise */
}
return [2 /*return*/, {
returnCode: returnCode,
errorMessage: common_1.errorCodeToString(returnCode),
testMode: response[0] !== 0,
major: response[1],
minor: response[2],
patch: response[3],
deviceLocked: response[4] === 1,
targetId: targetId.toString(16),
}];
case 2:
error_1 = _a.sent();
throw common_1.ledgerErrorFromResponse(error_1);
case 3: return [2 /*return*/];
}
});
});
}
exports.getVersion = getVersion;
function getPublicKey(transport, data) {
return __awaiter(this, void 0, void 0, function () {
var response, errorCodeData, returnCode, compressedPk, error_2;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
_a.trys.push([0, 2, , 3]);
return [4 /*yield*/, transport.send(common_1.CLA, common_1.INS.GET_ADDR_SECP256K1, 0, 0, data, [
common_1.LedgerErrorType.NoErrors,
])];
case 1:
response = _a.sent();
errorCodeData = response.slice(-2);
returnCode = errorCodeData[0] * 256 + errorCodeData[1];
compressedPk = Buffer.from(response.slice(0, 33));
return [2 /*return*/, {
compressedPk: compressedPk,
returnCode: returnCode,
errorMessage: common_1.errorCodeToString(returnCode),
}];
case 2:
error_2 = _a.sent();
throw common_1.ledgerErrorFromResponse(error_2);
case 3: return [2 /*return*/];
}
});
});
}
exports.getPublicKey = getPublicKey;
/**
* Takes raw output from Ledger device which is TLV encoded in the format "0x30 L 0x02 Lr r 0x02 Ls s"
* where L is length of entire message (after 2nd byte), Lr is length of r and Ls is length of s.
* This function extracts r and s, ensures they are 32 bytes each, joins into a single 64 byte Array.
* This value returned should be base64 encoded to a string to add to the JSON 'signatures' to send to RPC.
*
* Example raw sig:
* [ 48, 69, 2, 33,
* 0, 161, 207, 217, 127, 151, 190, 23, 98, 217, 186, 108, 82, 102, 19, 222, 20, 171, 6, 240, 134, 195, 251, 98, 190, 246, 228, 243, 215, 95, 166, 121, 165,
* 2, 32,
* 69, 188, 53, 213, 24, 9, 191, 90, 244, 21, 213, 146, 240, 109, 156, 221, 247, 63, 131, 52, 150, 253, 199, 153, 132, 76, 91, 239, 28, 254, 68, 80 ]
*
* 48 69 -- length is 69
* 2 33 -- length of r is 33 bytes
* <33 bytes of r> -- (the leading zero here can be dropped)
* 2 32 -- length of s is 32 bytes
* <32 bytes of s>
*/
function extractSignatureFromTLV(signatureArray) {
// Check Type Length Value encoding
if (signatureArray.length < 64) {
throw Error("Invalid Signature: Too short");
}
if (signatureArray[0] !== 0x30) {
throw Error("Invalid Ledger Signature TLV encoding: expected first byte 0x30");
}
if (signatureArray[1] + 2 !== signatureArray.length) {
throw Error("Invalid Signature: signature length does not match TLV");
}
if (signatureArray[2] !== 0x02) {
throw Error("Invalid Ledger Signature TLV encoding: expected length type 0x02");
}
// r signature
var rLength = signatureArray[3];
var rSignature = signatureArray.slice(4, rLength + 4);
// Drop leading zero on some 'r' signatures that are 33 bytes.
if (rSignature.length === 33 && rSignature[0] === 0) {
rSignature = rSignature.slice(1, 33);
}
else if (rSignature.length === 33) {
throw Error('Invalid signature: "r" too long');
}
// add leading zero's to pad to 32 bytes
while (rSignature.length < 32) {
Buffer.concat([Buffer.from([0]), rSignature]);
}
// s signature
if (signatureArray[rLength + 4] !== 0x02) {
throw Error("Invalid Ledger Signature TLV encoding: expected length type 0x02");
}
var sLength = signatureArray[rLength + 5];
if (4 + rLength + 2 + sLength != signatureArray.length) {
throw Error("Invalid Ledger Signature: TLV byte lengths do not match message length");
}
var sSignature = signatureArray.slice(rLength + 6, signatureArray.length);
// Drop leading zero on 's' signatures that are 33 bytes. This shouldn't occur since ledger signs using "Small s" math. But just to be sure...
if (sSignature.length === 33 && sSignature[0] === 0) {
sSignature = sSignature.slice(1, 33);
}
else if (sSignature.length === 33) {
throw Error('Invalid signature: "s" too long');
}
// add leading zero's to pad to 32 bytes
while (sSignature.length < 32) {
Buffer.concat([Buffer.from([0]), sSignature]);
}
if (rSignature.length !== 32 || sSignature.length !== 32) {
throw Error("Invalid signatures: must be 32 bytes each");
}
return Buffer.concat([rSignature, sSignature]);
}
exports.extractSignatureFromTLV = extractSignatureFromTLV;
//# sourceMappingURL=helper.js.map