ledger-bitsong-js
Version:
Node API for BitSong App (Ledger Nano S/X)
625 lines (531 loc) • 23.3 kB
JavaScript
"use strict";
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _regenerator = _interopRequireDefault(require("@babel/runtime/regenerator"));
var _asyncToGenerator2 = _interopRequireDefault(require("@babel/runtime/helpers/asyncToGenerator"));
var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck"));
var _createClass2 = _interopRequireDefault(require("@babel/runtime/helpers/createClass"));
var _crypto = _interopRequireDefault(require("crypto"));
var _ripemd = _interopRequireDefault(require("ripemd160"));
var _bech = _interopRequireDefault(require("bech32"));
var _helperV = require("./helperV1");
var _helperV2 = require("./helperV2");
var _common = require("./common");
/** ******************************************************************************
* (c) 2021 Bitsong
* (c) 2019 - 2021 ZondaX GmbH
* (c) 2016-2017 Ledger
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
******************************************************************************* */
var BitsongApp = /*#__PURE__*/function () {
function BitsongApp(transport) {
var scrambleKey = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : _common.APP_KEY;
(0, _classCallCheck2.default)(this, BitsongApp);
if (!transport) {
throw new Error("Transport has not been defined");
}
this.transport = transport;
transport.decorateAppAPIMethods(this, ["getVersion", "sign", "getAddressAndPubKey", "appInfo", "deviceInfo", "getBech32FromPK"], scrambleKey);
}
(0, _createClass2.default)(BitsongApp, [{
key: "serializePath",
value: function () {
var _serializePath = (0, _asyncToGenerator2.default)( /*#__PURE__*/_regenerator.default.mark(function _callee(path) {
return _regenerator.default.wrap(function _callee$(_context) {
while (1) {
switch (_context.prev = _context.next) {
case 0:
_context.next = 2;
return (0, _common.getVersion)(this.transport);
case 2:
this.versionResponse = _context.sent;
if (!(this.versionResponse.return_code !== _common.ERROR_CODE.NoError)) {
_context.next = 5;
break;
}
throw this.versionResponse;
case 5:
_context.t0 = this.versionResponse.major;
_context.next = _context.t0 === 1 ? 8 : _context.t0 === 2 ? 9 : 10;
break;
case 8:
return _context.abrupt("return", (0, _helperV.serializePathv1)(path));
case 9:
return _context.abrupt("return", (0, _helperV2.serializePathv2)(path));
case 10:
return _context.abrupt("return", {
return_code: 0x6400,
error_message: "App Version is not supported"
});
case 11:
case "end":
return _context.stop();
}
}
}, _callee, this);
}));
function serializePath(_x) {
return _serializePath.apply(this, arguments);
}
return serializePath;
}()
}, {
key: "signGetChunks",
value: function () {
var _signGetChunks = (0, _asyncToGenerator2.default)( /*#__PURE__*/_regenerator.default.mark(function _callee2(path, message) {
var serializedPath, chunks, buffer, i, end;
return _regenerator.default.wrap(function _callee2$(_context2) {
while (1) {
switch (_context2.prev = _context2.next) {
case 0:
_context2.next = 2;
return this.serializePath(path);
case 2:
serializedPath = _context2.sent;
chunks = [];
chunks.push(serializedPath);
buffer = Buffer.from(message);
for (i = 0; i < buffer.length; i += _common.CHUNK_SIZE) {
end = i + _common.CHUNK_SIZE;
if (i > buffer.length) {
end = buffer.length;
}
chunks.push(buffer.slice(i, end));
}
return _context2.abrupt("return", chunks);
case 8:
case "end":
return _context2.stop();
}
}
}, _callee2, this);
}));
function signGetChunks(_x2, _x3) {
return _signGetChunks.apply(this, arguments);
}
return signGetChunks;
}()
}, {
key: "getVersion",
value: function () {
var _getVersion2 = (0, _asyncToGenerator2.default)( /*#__PURE__*/_regenerator.default.mark(function _callee3() {
return _regenerator.default.wrap(function _callee3$(_context3) {
while (1) {
switch (_context3.prev = _context3.next) {
case 0:
_context3.prev = 0;
_context3.next = 3;
return (0, _common.getVersion)(this.transport);
case 3:
this.versionResponse = _context3.sent;
return _context3.abrupt("return", this.versionResponse);
case 7:
_context3.prev = 7;
_context3.t0 = _context3["catch"](0);
return _context3.abrupt("return", (0, _common.processErrorResponse)(_context3.t0));
case 10:
case "end":
return _context3.stop();
}
}
}, _callee3, this, [[0, 7]]);
}));
function getVersion() {
return _getVersion2.apply(this, arguments);
}
return getVersion;
}()
}, {
key: "appInfo",
value: function () {
var _appInfo = (0, _asyncToGenerator2.default)( /*#__PURE__*/_regenerator.default.mark(function _callee4() {
return _regenerator.default.wrap(function _callee4$(_context4) {
while (1) {
switch (_context4.prev = _context4.next) {
case 0:
return _context4.abrupt("return", this.transport.send(0xb0, 0x01, 0, 0).then(function (response) {
var errorCodeData = response.slice(-2);
var returnCode = errorCodeData[0] * 256 + errorCodeData[1];
var result = {};
var appName = "err";
var appVersion = "err";
var flagLen = 0;
var flagsValue = 0;
if (response[0] !== 1) {
// Ledger responds with format ID 1. There is no spec for any format != 1
result.error_message = "response format ID not recognized";
result.return_code = 0x9001;
} else {
var appNameLen = response[1];
appName = response.slice(2, 2 + appNameLen).toString("ascii");
var idx = 2 + appNameLen;
var appVersionLen = response[idx];
idx += 1;
appVersion = response.slice(idx, idx + appVersionLen).toString("ascii");
idx += appVersionLen;
var appFlagsLen = response[idx];
idx += 1;
flagLen = appFlagsLen;
flagsValue = response[idx];
}
return {
return_code: returnCode,
error_message: (0, _common.errorCodeToString)(returnCode),
// //
appName: appName,
appVersion: appVersion,
flagLen: flagLen,
flagsValue: flagsValue,
// eslint-disable-next-line no-bitwise
flag_recovery: (flagsValue & 1) !== 0,
// eslint-disable-next-line no-bitwise
flag_signed_mcu_code: (flagsValue & 2) !== 0,
// eslint-disable-next-line no-bitwise
flag_onboarded: (flagsValue & 4) !== 0,
// eslint-disable-next-line no-bitwise
flag_pin_validated: (flagsValue & 128) !== 0
};
}, _common.processErrorResponse));
case 1:
case "end":
return _context4.stop();
}
}
}, _callee4, this);
}));
function appInfo() {
return _appInfo.apply(this, arguments);
}
return appInfo;
}()
}, {
key: "deviceInfo",
value: function () {
var _deviceInfo = (0, _asyncToGenerator2.default)( /*#__PURE__*/_regenerator.default.mark(function _callee5() {
return _regenerator.default.wrap(function _callee5$(_context5) {
while (1) {
switch (_context5.prev = _context5.next) {
case 0:
return _context5.abrupt("return", this.transport.send(0xe0, 0x01, 0, 0, Buffer.from([]), [_common.ERROR_CODE.NoError, 0x6e00]).then(function (response) {
var errorCodeData = response.slice(-2);
var returnCode = errorCodeData[0] * 256 + errorCodeData[1];
if (returnCode === 0x6e00) {
return {
return_code: returnCode,
error_message: "This command is only available in the Dashboard"
};
}
var targetId = response.slice(0, 4).toString("hex");
var pos = 4;
var secureElementVersionLen = response[pos];
pos += 1;
var seVersion = response.slice(pos, pos + secureElementVersionLen).toString();
pos += secureElementVersionLen;
var flagsLen = response[pos];
pos += 1;
var flag = response.slice(pos, pos + flagsLen).toString("hex");
pos += flagsLen;
var mcuVersionLen = response[pos];
pos += 1; // Patch issue in mcu version
var tmp = response.slice(pos, pos + mcuVersionLen);
if (tmp[mcuVersionLen - 1] === 0) {
tmp = response.slice(pos, pos + mcuVersionLen - 1);
}
var mcuVersion = tmp.toString();
return {
return_code: returnCode,
error_message: (0, _common.errorCodeToString)(returnCode),
// //
targetId: targetId,
seVersion: seVersion,
flag: flag,
mcuVersion: mcuVersion
};
}, _common.processErrorResponse));
case 1:
case "end":
return _context5.stop();
}
}
}, _callee5, this);
}));
function deviceInfo() {
return _deviceInfo.apply(this, arguments);
}
return deviceInfo;
}()
}, {
key: "publicKey",
value: function () {
var _publicKey = (0, _asyncToGenerator2.default)( /*#__PURE__*/_regenerator.default.mark(function _callee6(path) {
var serializedPath, data;
return _regenerator.default.wrap(function _callee6$(_context6) {
while (1) {
switch (_context6.prev = _context6.next) {
case 0:
_context6.prev = 0;
_context6.next = 3;
return this.serializePath(path);
case 3:
serializedPath = _context6.sent;
_context6.t0 = this.versionResponse.major;
_context6.next = _context6.t0 === 1 ? 7 : _context6.t0 === 2 ? 8 : 10;
break;
case 7:
return _context6.abrupt("return", (0, _helperV.publicKeyv1)(this, serializedPath));
case 8:
data = Buffer.concat([BitsongApp.serializeHRP("bitsong"), serializedPath]);
return _context6.abrupt("return", (0, _helperV2.publicKeyv2)(this, data));
case 10:
return _context6.abrupt("return", {
return_code: 0x6400,
error_message: "App Version is not supported"
});
case 11:
_context6.next = 16;
break;
case 13:
_context6.prev = 13;
_context6.t1 = _context6["catch"](0);
return _context6.abrupt("return", (0, _common.processErrorResponse)(_context6.t1));
case 16:
case "end":
return _context6.stop();
}
}
}, _callee6, this, [[0, 13]]);
}));
function publicKey(_x4) {
return _publicKey.apply(this, arguments);
}
return publicKey;
}()
}, {
key: "getAddressAndPubKey",
value: function () {
var _getAddressAndPubKey = (0, _asyncToGenerator2.default)( /*#__PURE__*/_regenerator.default.mark(function _callee7(path, hrp) {
var _this = this;
return _regenerator.default.wrap(function _callee7$(_context7) {
while (1) {
switch (_context7.prev = _context7.next) {
case 0:
_context7.prev = 0;
return _context7.abrupt("return", this.serializePath(path).then(function (serializedPath) {
var data = Buffer.concat([BitsongApp.serializeHRP(hrp), serializedPath]);
return _this.transport.send(_common.CLA, _common.INS.GET_ADDR_SECP256K1, _common.P1_VALUES.ONLY_RETRIEVE, 0, data, [_common.ERROR_CODE.NoError]).then(function (response) {
var errorCodeData = response.slice(-2);
var returnCode = errorCodeData[0] * 256 + errorCodeData[1];
var compressedPk = Buffer.from(response.slice(0, 33));
var bech32Address = Buffer.from(response.slice(33, -2)).toString();
return {
bech32_address: bech32Address,
compressed_pk: compressedPk,
return_code: returnCode,
error_message: (0, _common.errorCodeToString)(returnCode)
};
}, _common.processErrorResponse);
}).catch(function (err) {
return (0, _common.processErrorResponse)(err);
}));
case 4:
_context7.prev = 4;
_context7.t0 = _context7["catch"](0);
return _context7.abrupt("return", (0, _common.processErrorResponse)(_context7.t0));
case 7:
case "end":
return _context7.stop();
}
}
}, _callee7, this, [[0, 4]]);
}));
function getAddressAndPubKey(_x5, _x6) {
return _getAddressAndPubKey.apply(this, arguments);
}
return getAddressAndPubKey;
}()
}, {
key: "showAddressAndPubKey",
value: function () {
var _showAddressAndPubKey = (0, _asyncToGenerator2.default)( /*#__PURE__*/_regenerator.default.mark(function _callee8(path, hrp) {
var _this2 = this;
return _regenerator.default.wrap(function _callee8$(_context8) {
while (1) {
switch (_context8.prev = _context8.next) {
case 0:
_context8.prev = 0;
return _context8.abrupt("return", this.serializePath(path).then(function (serializedPath) {
var data = Buffer.concat([BitsongApp.serializeHRP(hrp), serializedPath]);
return _this2.transport.send(_common.CLA, _common.INS.GET_ADDR_SECP256K1, _common.P1_VALUES.SHOW_ADDRESS_IN_DEVICE, 0, data, [_common.ERROR_CODE.NoError]).then(function (response) {
var errorCodeData = response.slice(-2);
var returnCode = errorCodeData[0] * 256 + errorCodeData[1];
var compressedPk = Buffer.from(response.slice(0, 33));
var bech32Address = Buffer.from(response.slice(33, -2)).toString();
return {
bech32_address: bech32Address,
compressed_pk: compressedPk,
return_code: returnCode,
error_message: (0, _common.errorCodeToString)(returnCode)
};
}, _common.processErrorResponse);
}).catch(function (err) {
return (0, _common.processErrorResponse)(err);
}));
case 4:
_context8.prev = 4;
_context8.t0 = _context8["catch"](0);
return _context8.abrupt("return", (0, _common.processErrorResponse)(_context8.t0));
case 7:
case "end":
return _context8.stop();
}
}
}, _callee8, this, [[0, 4]]);
}));
function showAddressAndPubKey(_x7, _x8) {
return _showAddressAndPubKey.apply(this, arguments);
}
return showAddressAndPubKey;
}()
}, {
key: "signSendChunk",
value: function () {
var _signSendChunk = (0, _asyncToGenerator2.default)( /*#__PURE__*/_regenerator.default.mark(function _callee9(chunkIdx, chunkNum, chunk) {
return _regenerator.default.wrap(function _callee9$(_context9) {
while (1) {
switch (_context9.prev = _context9.next) {
case 0:
_context9.t0 = this.versionResponse.major;
_context9.next = _context9.t0 === 1 ? 3 : _context9.t0 === 2 ? 4 : 5;
break;
case 3:
return _context9.abrupt("return", (0, _helperV.signSendChunkv1)(this, chunkIdx, chunkNum, chunk));
case 4:
return _context9.abrupt("return", (0, _helperV2.signSendChunkv2)(this, chunkIdx, chunkNum, chunk));
case 5:
return _context9.abrupt("return", {
return_code: 0x6400,
error_message: "App Version is not supported"
});
case 6:
case "end":
return _context9.stop();
}
}
}, _callee9, this);
}));
function signSendChunk(_x9, _x10, _x11) {
return _signSendChunk.apply(this, arguments);
}
return signSendChunk;
}()
}, {
key: "sign",
value: function () {
var _sign = (0, _asyncToGenerator2.default)( /*#__PURE__*/_regenerator.default.mark(function _callee11(path, message) {
var _this3 = this;
return _regenerator.default.wrap(function _callee11$(_context11) {
while (1) {
switch (_context11.prev = _context11.next) {
case 0:
return _context11.abrupt("return", this.signGetChunks(path, message).then(function (chunks) {
return _this3.signSendChunk(1, chunks.length, chunks[0], [_common.ERROR_CODE.NoError]).then( /*#__PURE__*/function () {
var _ref = (0, _asyncToGenerator2.default)( /*#__PURE__*/_regenerator.default.mark(function _callee10(response) {
var result, i;
return _regenerator.default.wrap(function _callee10$(_context10) {
while (1) {
switch (_context10.prev = _context10.next) {
case 0:
result = {
return_code: response.return_code,
error_message: response.error_message,
signature: null
};
i = 1;
case 2:
if (!(i < chunks.length)) {
_context10.next = 11;
break;
}
_context10.next = 5;
return _this3.signSendChunk(1 + i, chunks.length, chunks[i]);
case 5:
result = _context10.sent;
if (!(result.return_code !== _common.ERROR_CODE.NoError)) {
_context10.next = 8;
break;
}
return _context10.abrupt("break", 11);
case 8:
i += 1;
_context10.next = 2;
break;
case 11:
return _context10.abrupt("return", {
return_code: result.return_code,
error_message: result.error_message,
// ///
signature: result.signature
});
case 12:
case "end":
return _context10.stop();
}
}
}, _callee10);
}));
return function (_x14) {
return _ref.apply(this, arguments);
};
}(), _common.processErrorResponse);
}, _common.processErrorResponse));
case 1:
case "end":
return _context11.stop();
}
}
}, _callee11, this);
}));
function sign(_x12, _x13) {
return _sign.apply(this, arguments);
}
return sign;
}()
}], [{
key: "serializeHRP",
value: function serializeHRP(hrp) {
if (hrp == null || hrp.length < 3 || hrp.length > 83) {
throw new Error("Invalid HRP");
}
var buf = Buffer.alloc(1 + hrp.length);
buf.writeUInt8(hrp.length, 0);
buf.write(hrp, 1);
return buf;
}
}, {
key: "getBech32FromPK",
value: function getBech32FromPK(hrp, pk) {
if (pk.length !== 33) {
throw new Error("expected compressed public key [31 bytes]");
}
var hashSha256 = _crypto.default.createHash("sha256").update(pk).digest();
var hashRip = new _ripemd.default().update(hashSha256).digest();
return _bech.default.encode(hrp, _bech.default.toWords(hashRip));
}
}]);
return BitsongApp;
}();
exports.default = BitsongApp;