@fluent-wallet/hw-app-conflux
Version:
Ledger Hardware Wallet Conflux Application API
409 lines • 18.5 kB
JavaScript
"use strict";
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 (g && (g = 0, op[0] && (_ = 0)), _) 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 __values = (this && this.__values) || function(o) {
var s = typeof Symbol === "function" && Symbol.iterator, m = s && o[s], i = 0;
if (m) return m.call(o);
if (o && typeof o.length === "number") return {
next: function () {
if (o && i >= o.length) o = void 0;
return { value: o && o[i++], done: !o };
}
};
throw new TypeError(s ? "Object is not iterable." : "Symbol.iterator is not defined.");
};
var __read = (this && this.__read) || function (o, n) {
var m = typeof Symbol === "function" && o[Symbol.iterator];
if (!m) return o;
var i = m.call(o), r, ar = [], e;
try {
while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);
}
catch (error) { e = { error: error }; }
finally {
try {
if (r && !r.done && (m = i["return"])) m.call(i);
}
finally { if (e) throw e.error; }
}
return ar;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
exports.__esModule = true;
var utils_1 = require("./utils");
var js_conflux_sdk_1 = require("js-conflux-sdk");
var bip32_path_1 = __importDefault(require("bip32-path"));
var remapTransactionRelatedErrors = function (e) {
if (e && e.statusCode === 0x6a80) {
throw new Error("Missing a parameter. Try enabling blind signature in the app");
}
return e;
};
var CLA = 0xe0;
var P1 = {
first: 0x00
};
var P2 = {
more: 0x80,
last: 0x00
};
var INS = {
GET_ADDRESS: 0x02,
SIGN_TX: 0x03,
SIGN_PERSONAL_MESSAGE: 0x04
};
var CHAINID = {
MAINNET: 1029,
TESTNET: 1
};
/**
* Conflux API
*
* @param transport a transport for sending commands to a device
* @param scrambleKey a scramble key
*
* @example
* import Cfx from "@ledgerhq/hw-app-conflux";
* const cfx = new Cfx(transport)
*/
var Conflux = /** @class */ (function () {
function Conflux(transport, chainId, scrambleKey) {
if (scrambleKey === void 0) { scrambleKey = "conflux_default_scramble_key"; }
this.transport = transport;
this.chainId = chainId || CHAINID.MAINNET;
transport.decorateAppAPIMethods(this, [
"getAddress",
"signTransaction",
"getAppConfiguration",
"signPersonalMessage",
], scrambleKey);
}
/**
* get Conflux address for a given BIP 32 path.
* @param path a path in BIP 32 format
* @option boolDisplay optionally enable or not the display
* @option boolChaincode optionally enable or not the chaincode request
* @return an object with a publicKey, address and (optionally) chainCode
* @example
* cfx.getAddress("44'/503'/0'/0/0").then(o => o.publicKey)
* cfx.getAddress("44'/503'/0'/0/0",true).then(o => o.publicKey): show mainnet address
*/
Conflux.prototype.getAddress = function (path, boolDisplay, boolChaincode) {
var _this = this;
//path buffer
var paths = (0, utils_1.splitPath)(path);
var buffer = Buffer.alloc(1 + paths.length * 4);
buffer[0] = paths.length;
paths.forEach(function (element, index) {
buffer.writeUInt32BE(element, 1 + 4 * index);
});
//chainID buffer
if (boolDisplay) {
var chainIdBuffer = Buffer.alloc(4);
chainIdBuffer.writeUInt32BE(this.chainId);
buffer = Buffer.concat([buffer, chainIdBuffer]);
}
return this.transport
.send(0xe0, INS.GET_ADDRESS, boolDisplay ? 0x01 : 0x00, boolChaincode ? 0x01 : 0x00, buffer)
.then(function (response) {
var publicKeyLength = response[0];
var publicKey = response
.slice(2, 1 + publicKeyLength)
.toString("hex"); // remove the prefix:04, because 04 means the uncompressed public key
var address = js_conflux_sdk_1.format.address("0x".concat(js_conflux_sdk_1.sign["publicKeyToAddress"](Buffer.from(publicKey, "hex")).toString("hex")), _this.chainId); //CIP-37 address
var chainCode;
if (boolChaincode) {
var chainCodeLength = response[1 + publicKeyLength];
chainCode = response
.slice(1 + publicKeyLength + 1, 1 + publicKeyLength + 1 + chainCodeLength)
.toString("hex");
}
return {
publicKey: publicKey,
address: address,
chainCode: chainCode
};
});
};
Conflux.prototype._legacy_signTransaction = function (path, rawTxHex) {
return __awaiter(this, void 0, void 0, function () {
var paths, offset, rawTx, toSend, response, _loop_1;
var _this = this;
return __generator(this, function (_a) {
paths = bip32_path_1["default"].fromString(path).toPathArray();
offset = 0;
rawTx = Buffer.from(rawTxHex, "hex");
toSend = [];
_loop_1 = function () {
var maxChunkSize = offset === 0 ? 150 - 1 - paths.length * 4 : 150;
var chunkSize = offset + maxChunkSize > rawTx.length
? rawTx.length - offset
: maxChunkSize;
var buffer = Buffer.alloc(offset === 0 ? 1 + paths.length * 4 + chunkSize : chunkSize);
if (offset === 0) {
buffer[0] = paths.length;
paths.forEach(function (element, index) {
buffer.writeUInt32BE(element, 1 + 4 * index);
});
rawTx.copy(buffer, 1 + 4 * paths.length, offset, offset + chunkSize);
}
else {
rawTx.copy(buffer, 0, offset, offset + chunkSize);
}
toSend.push(buffer);
offset += chunkSize;
};
while (offset !== rawTx.length) {
_loop_1();
}
return [2 /*return*/, (0, utils_1.foreach)(toSend, function (data, i) {
return _this.transport
.send(0xe0, INS.SIGN_TX, i === 0 ? 0x00 : 0x80, 0x00, data)
.then(function (apduResponse) {
response = apduResponse;
});
}).then(function () {
var response_byte = response.slice(0, 1)[0];
var v = response_byte.toString(16);
var r = response.slice(1, 1 + 32).toString("hex");
var s = response.slice(1 + 32, 1 + 32 + 32).toString("hex");
return {
v: v,
r: r,
s: s
};
}, function (e) {
throw remapTransactionRelatedErrors(e);
})];
});
});
};
Conflux.prototype._signTransaction = function (path, rawTxHex) {
return __awaiter(this, void 0, void 0, function () {
var rawTx, paths, derivationPathBuff, payloadChunks, i, chunk, response, response_byte, v, r, s;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
rawTx = Buffer.from(rawTxHex, "hex");
paths = (0, utils_1.splitPath)(path);
derivationPathBuff = Buffer.alloc(1 + paths.length * 4);
derivationPathBuff[0] = paths.length;
paths.forEach(function (element, index) {
derivationPathBuff.writeUInt32BE(element, 1 + 4 * index);
});
// send bip32
return [4 /*yield*/, this.transport.send(CLA, INS.SIGN_TX, P1.first, P2.more, derivationPathBuff)];
case 1:
// send bip32
_a.sent();
payloadChunks = (0, utils_1.splitMessage)(rawTx, 255);
if (!(payloadChunks.length > 1)) return [3 /*break*/, 5];
i = 0;
_a.label = 2;
case 2:
if (!(i < payloadChunks.length - 1)) return [3 /*break*/, 5];
chunk = payloadChunks[i];
return [4 /*yield*/, this.transport.send(CLA, INS.SIGN_TX, i + 1, P2.more, chunk)];
case 3:
_a.sent();
_a.label = 4;
case 4:
i++;
return [3 /*break*/, 2];
case 5: return [4 /*yield*/, this.transport.send(CLA, INS.SIGN_TX, Math.max(payloadChunks.length - 1, 1), P2.last, payloadChunks[payloadChunks.length - 1])];
case 6:
response = _a.sent();
response_byte = response.subarray(0, 1)[0];
v = response_byte.toString(16);
r = response.subarray(1, 1 + 32).toString("hex");
s = response.subarray(1 + 32, 1 + 32 + 32).toString("hex");
return [2 /*return*/, {
v: v,
r: r,
s: s
}];
}
});
});
};
/**
* You can sign a transaction and retrieve v, r, s given the raw transaction and the BIP 32 path of the account to sign
* @example
cfx.signTransaction("44'/503'/0'/0/0", "e8018504e3b292008252089428ee52a8f3d6e5d15f8b131996950d7f296c7952872bd72a2487400080").then(result => ...)
*/
Conflux.prototype.signTransaction = function (path, rawTxHex) {
return __awaiter(this, void 0, void 0, function () {
var version, isLegacy;
return __generator(this, function (_a) {
switch (_a.label) {
case 0: return [4 /*yield*/, this._getAppConfiguration()];
case 1:
version = (_a.sent()).version;
isLegacy = (0, utils_1.isLegacyVersion)(version);
if (isLegacy)
return [2 /*return*/, this._legacy_signTransaction(path, rawTxHex)];
return [2 /*return*/, this._signTransaction(path, rawTxHex)];
}
});
});
};
Conflux.prototype._getAppConfiguration = function () {
return __awaiter(this, void 0, void 0, function () {
var r, i, format, nameLength, name, versionLength, version, flagLength, flags;
return __generator(this, function (_a) {
switch (_a.label) {
case 0: return [4 /*yield*/, this.transport.send(0xb0, 0x01, 0x00, 0x00)];
case 1:
r = _a.sent();
i = 0;
format = r[i++];
if (format !== 1) {
throw new Error("getAppAndVersion: format not supported");
}
nameLength = r[i++];
name = r.slice(i, (i += nameLength)).toString("ascii");
versionLength = r[i++];
version = r.slice(i, (i += versionLength)).toString("ascii");
flagLength = r[i++];
flags = r.slice(i, (i += flagLength));
return [2 /*return*/, {
name: name,
version: version,
flags: flags
}];
}
});
});
};
Conflux.prototype.getAppConfiguration = function () {
return __awaiter(this, void 0, void 0, function () {
return __generator(this, function (_a) {
return [2 /*return*/, this._getAppConfiguration()];
});
});
};
/**
* You can sign a message according to cfx_sign RPC call and retrieve v, r, s given the message and the BIP 32 path of the account to sign.
* @example cfx.signPersonalMessage("44'/503'/0'/0/0", Buffer.from("test").toString("hex"))
* @param path hdPath
* @param messageHex the hex string of the message
* @returns
*/
Conflux.prototype.signPersonalMessage = function (path, messageHex) {
var _this = this;
var paths = (0, utils_1.splitPath)(path);
var offset = 0;
var message = Buffer.from(messageHex, "hex");
var toSend = [];
var response;
var _loop_2 = function () {
var maxChunkSize = offset === 0 ? 150 - 1 - paths.length * 4 - 4 - 4 : 150;
var chunkSize = offset + maxChunkSize > message.length
? message.length - offset
: maxChunkSize;
var buffer = Buffer.alloc(offset === 0 ? 1 + paths.length * 4 + 4 + 4 + chunkSize : chunkSize);
if (offset === 0) {
buffer[0] = paths.length;
paths.forEach(function (element, index) {
buffer.writeUInt32BE(element, 1 + 4 * index);
});
buffer.writeUInt32BE(this_1.chainId, 1 + paths.length * 4);
buffer.writeUInt32BE(message.length, 1 + 4 * paths.length + 4);
message.copy(buffer, 1 + 4 * paths.length + 4 + 4, offset, offset + chunkSize);
}
else {
message.copy(buffer, 0, offset, offset + chunkSize);
}
toSend.push(buffer);
offset += chunkSize;
};
var this_1 = this;
while (offset !== message.length) {
_loop_2();
}
return (0, utils_1.foreach)(toSend, function (data, i) {
return _this.transport
.send(0xe0, INS.SIGN_PERSONAL_MESSAGE, i === 0 ? 0x00 : 0x80, 0x00, data)
.then(function (apduResponse) {
response = apduResponse;
});
}).then(function () {
var v = response[0];
var r = response.slice(1, 1 + 32).toString("hex");
var s = response.slice(1 + 32, 1 + 32 + 32).toString("hex");
return {
v: v,
r: r,
s: s
};
});
};
Conflux.prototype.pathToBuffer = function (originalPath) {
var path = originalPath
.split("/")
.map(function (value) {
return value.endsWith("'") || value.endsWith("h") ? value : value + "'";
})
.join("/");
var pathNums = bip32_path_1["default"].fromString(path).toPathArray();
return this.serializePath(pathNums);
};
Conflux.prototype.serializePath = function (path) {
var e_1, _a;
var buf = Buffer.alloc(1 + path.length * 4);
buf.writeUInt8(path.length, 0);
try {
for (var _b = __values(path.entries()), _c = _b.next(); !_c.done; _c = _b.next()) {
var _d = __read(_c.value, 2), i = _d[0], num = _d[1];
buf.writeUInt32BE(num, 1 + i * 4);
}
}
catch (e_1_1) { e_1 = { error: e_1_1 }; }
finally {
try {
if (_c && !_c.done && (_a = _b["return"])) _a.call(_b);
}
finally { if (e_1) throw e_1.error; }
}
return buf;
};
return Conflux;
}());
exports["default"] = Conflux;
//# sourceMappingURL=Conflux.js.map