UNPKG

jadets

Version:
656 lines (648 loc) 21 kB
var __defProp = Object.defineProperty; var __getOwnPropSymbols = Object.getOwnPropertySymbols; var __hasOwnProp = Object.prototype.hasOwnProperty; var __propIsEnum = Object.prototype.propertyIsEnumerable; var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; var __spreadValues = (a, b) => { for (var prop in b || (b = {})) if (__hasOwnProp.call(b, prop)) __defNormalProp(a, prop, b[prop]); if (__getOwnPropSymbols) for (var prop of __getOwnPropSymbols(b)) { if (__propIsEnum.call(b, prop)) __defNormalProp(a, prop, b[prop]); } return a; }; var __async = (__this, __arguments, generator) => { return new Promise((resolve, reject) => { var fulfilled = (value) => { try { step(generator.next(value)); } catch (e) { reject(e); } }; var rejected = (value) => { try { step(generator.throw(value)); } catch (e) { reject(e); } }; var step = (x) => x.done ? resolve(x.value) : Promise.resolve(x.value).then(fulfilled, rejected); step((generator = generator.apply(__this, __arguments)).next()); }); }; // src/transport/SerialTransport.ts import { EventEmitter } from "events"; import { encode, decode } from "cbor2"; var SerialTransport = class extends EventEmitter { constructor(options) { super(); this.port = null; this.reader = null; this.receivedBuffer = new Uint8Array(0); this.options = options; } drain() { this.receivedBuffer = new Uint8Array(0); } connect() { return __async(this, null, function* () { var _a; try { const serial = navigator.serial; if (!serial) { throw new Error("Web Serial API is not supported in this browser."); } const ports = yield serial.getPorts(); if (ports.length === 0) { this.port = yield serial.requestPort(); } else { this.port = ports[0]; } if (!this.port) { throw new Error("No serial port selected."); } yield this.port.open({ baudRate: this.options.baudRate || 115200, bufferSize: this.options.bufferSize || 4 * 1024 }); this.reader = ((_a = this.port.readable) == null ? void 0 : _a.getReader()) || null; if (this.reader) { this.readLoop(); } } catch (error) { console.error("[WebSerialPort] Failed to connect:", error); throw error; } }); } readLoop() { return __async(this, null, function* () { if (!this.reader) return; try { while (true) { const { value, done } = yield this.reader.read(); if (done) { break; } if (value) { this.receivedBuffer = this.concatBuffers(this.receivedBuffer, value); this.processReceivedData(); } } } catch (error) { console.error("[WebSerialPort] Read error:", error); } finally { if (this.reader) { this.reader.releaseLock(); this.reader = null; } } }); } processReceivedData() { let index = 1; while (index <= this.receivedBuffer.length) { try { const sliceToTry = this.receivedBuffer.slice(0, index); const decoded = decode(sliceToTry); if (decoded && typeof decoded === "object" && ("error" in decoded || "result" in decoded || "log" in decoded || "method" in decoded)) { this.emit("message", decoded); } this.receivedBuffer = this.receivedBuffer.slice(index); index = 1; } catch (error) { if (error.message && (error.message.includes("Offset is outside") || error.message.includes("Insufficient data") || error.message.includes("Unexpected end of stream"))) { index++; if (index > this.receivedBuffer.length) { break; } } else { console.error("[WebSerialPort] CBOR decode error:", error); this.receivedBuffer = new Uint8Array(0); break; } } } } concatBuffers(a, b) { const result = new Uint8Array(a.length + b.length); result.set(a); result.set(b, a.length); return result; } disconnect() { return __async(this, null, function* () { try { if (this.reader) { yield this.reader.cancel(); } if (this.port) { yield this.port.close(); } this.port = null; this.reader = null; } catch (error) { console.error("[WebSerialPort] Error during disconnect:", error); } }); } sendMessage(message) { return __async(this, null, function* () { try { if (!this.port || !this.port.writable) { throw new Error("Port not available"); } const encoded = encode(message); const writer = this.port.writable.getWriter(); yield writer.write(encoded); writer.releaseLock(); } catch (error) { console.error("[WebSerialPort] Failed to send message:", error); throw error; } }); } onMessage(callback) { this.on("message", callback); } }; // src/transport/TCPTransport.ts import { EventEmitter as EventEmitter2 } from "events"; import net from "net"; import { encode as encode2, decode as decode2 } from "cbor2"; var TCPTransport = class extends EventEmitter2 { constructor(host, port) { super(); this.host = host; this.port = port; this.socket = null; this.recvBuffer = Buffer.alloc(0); } connect() { return __async(this, null, function* () { return new Promise((resolve, reject) => { this.socket = new net.Socket(); this.socket.once("error", reject); this.socket.connect(this.port, this.host, () => { this.socket.on("data", this.onData.bind(this)).on("error", (err) => this.emit("error", err)).on("close", () => this.emit("disconnect")); resolve(); }); }); }); } disconnect() { return __async(this, null, function* () { if (!this.socket) return; return new Promise((resolve) => { this.socket.end(() => { this.socket = null; resolve(); }); }); }); } sendMessage(msg) { return __async(this, null, function* () { if (!this.socket) throw new Error("Not connected"); const chunk = encode2(msg); this.socket.write(chunk); }); } onMessage(callback) { this.on("message", callback); } onData(data) { this.recvBuffer = Buffer.concat([this.recvBuffer, data]); while (this.recvBuffer.length > 0) { try { const obj = decode2(this.recvBuffer); this.emit("message", obj); const consumed = encode2(obj).length; this.recvBuffer = this.recvBuffer.slice(consumed); } catch (e) { if (e.message.includes("Insufficient data") || e.message.includes("Unexpected end of stream")) { break; } this.emit("error", e); this.recvBuffer = Buffer.alloc(0); break; } } } }; // src/interfaces/JadeInterface.ts var JadeInterface = class { constructor(transport) { this.transport = transport; } connect() { return __async(this, null, function* () { yield this.transport.connect(); }); } disconnect() { return __async(this, null, function* () { yield this.transport.disconnect(); }); } buildRequest(id, method, params) { return { id, method, params }; } /** * Makes an RPC call and handles extended data responses automatically */ makeRPCCall(request, long_timeout = false) { return __async(this, null, function* () { if (!request.id || request.id.length > 16) { throw new Error("Request id must be non-empty and less than 16 characters"); } if (!request.method || request.method.length > 32) { throw new Error("Request method must be non-empty and less than 32 characters"); } yield this.transport.sendMessage(request); const initialResponse = yield this.waitForResponse(request.id, long_timeout); if (this.isExtendedDataResponse(initialResponse)) { return yield this.handleExtendedDataResponse(initialResponse, request.id, long_timeout); } return initialResponse; }); } /** * Waits for a single response message */ waitForResponse(requestId, long_timeout) { return __async(this, null, function* () { return new Promise((resolve, reject) => { const onResponse = (msg) => { if (msg && msg.id === requestId) { this.transport.removeListener("message", onResponse); if (timeoutId) clearTimeout(timeoutId); resolve(msg); } }; this.transport.onMessage(onResponse); let timeoutId; if (!long_timeout) { timeoutId = setTimeout(() => { this.transport.removeListener("message", onResponse); reject(new Error("RPC call timed out")); }, 5e3); } }); }); } /** * Checks if a response indicates extended data */ isExtendedDataResponse(response) { return response.seqnum !== void 0 && response.seqlen !== void 0 && response.seqnum < response.seqlen; } /** * Handles extended data responses by collecting all chunks */ handleExtendedDataResponse(initialResponse, requestId, long_timeout) { return __async(this, null, function* () { const chunks = [initialResponse]; const totalChunks = initialResponse.seqlen; console.log(`Receiving extended data: chunk ${initialResponse.seqnum + 1}/${totalChunks}`); for (let expectedSeqnum = initialResponse.seqnum + 1; expectedSeqnum < totalChunks; expectedSeqnum++) { const extendedRequest = { id: requestId, method: "get_extended_data", params: { seqnum: expectedSeqnum } }; yield this.transport.sendMessage(extendedRequest); const chunkResponse = yield this.waitForResponse(requestId, long_timeout); if (chunkResponse.seqnum !== expectedSeqnum) { throw new Error(`Expected chunk ${expectedSeqnum}, got ${chunkResponse.seqnum}`); } if (chunkResponse.seqlen !== totalChunks) { throw new Error(`Inconsistent seqlen: expected ${totalChunks}, got ${chunkResponse.seqlen}`); } chunks.push(chunkResponse); console.log(`Received extended data chunk: ${expectedSeqnum + 1}/${totalChunks}`); } return this.reassembleExtendedData(chunks); }); } /** * Reassembles chunks into a complete response */ reassembleExtendedData(chunks) { chunks.sort((a, b) => (a.seqnum || 0) - (b.seqnum || 0)); const completeResponse = { id: chunks[0].id, error: chunks[0].error }; if (completeResponse.error) { return completeResponse; } completeResponse.result = this.mergeChunkData(chunks); console.log(`Extended data reassembly complete: ${chunks.length} chunks processed`); return completeResponse; } mergeChunkData(chunks) { const firstResult = chunks[0].result; if (firstResult instanceof Uint8Array) { const totalLength = chunks.reduce((sum, chunk) => { const chunkData = chunk.result; return sum + chunkData.length; }, 0); const merged = new Uint8Array(totalLength); let offset = 0; for (const chunk of chunks) { const chunkData = chunk.result; merged.set(chunkData, offset); offset += chunkData.length; } return merged; } if (typeof Buffer !== "undefined" && Buffer.isBuffer(firstResult)) { return Buffer.concat(chunks.map((chunk) => chunk.result)); } if (Array.isArray(firstResult)) { return chunks.reduce((merged, chunk) => { return merged.concat(chunk.result); }, []); } if (typeof firstResult === "string") { return chunks.map((chunk) => chunk.result).join(""); } if (typeof firstResult === "object" && firstResult !== null) { return chunks.reduce((merged, chunk) => { return __spreadValues(__spreadValues({}, merged), chunk.result); }, {}); } console.warn("Unknown data type for extended data merge, returning first chunk"); return firstResult; } }; // src/utils/getFingerPrint.ts import * as ecc from "tiny-secp256k1"; import BIP32Factory from "bip32"; import { Buffer as Buffer2 } from "buffer"; var bip32 = BIP32Factory(ecc); var MAINNET_BIP32 = { wif: 128, bip32: { public: 76067358, // “xpub” private: 76066276 // “xprv” }, messagePrefix: "Bitcoin Signed Message:\n", bech32: "bc", pubKeyHash: 0, scriptHash: 5 }; var TESTNET_BIP32 = { wif: 239, bip32: { public: 70617039, // “tpub” private: 70615956 // “tprv” }, messagePrefix: "Bitcoin Signed Message:\n", bech32: "tb", pubKeyHash: 111, scriptHash: 196 }; function getFingerprintFromXpub(xpub, networkType) { try { const network = networkType === "testnet" ? TESTNET_BIP32 : MAINNET_BIP32; const node = bip32.fromBase58(xpub, network); return Buffer2.from(node.fingerprint).toString("hex"); } catch (err) { console.error("Error getting fingerprint:", err); return null; } } // src/utils/hexToBytes.ts function hexToBytes(hex) { if (hex.length % 2 !== 0) { throw new Error("hex string must have even length"); } const bytes = new Uint8Array(hex.length / 2); for (let i = 0; i < hex.length; i += 2) { bytes[i / 2] = parseInt(hex.slice(i, i + 2), 16); } return bytes; } // src/utils/base64ToBytes.ts function base64ToBytes(b64) { return Uint8Array.from(Buffer.from(b64, "base64")); } // src/utils/bytesToBase64.ts function bytesToBase64(bytes) { return Buffer.from(bytes).toString("base64"); } // src/Jade.ts import { randomBytes } from "crypto"; var Jade = class { constructor(iface) { this.iface = iface; } connect() { return __async(this, null, function* () { return this.iface.connect(); }); } disconnect() { return __async(this, null, function* () { return this.iface.disconnect(); }); } _jadeRpc(method, params, id, long_timeout = false, http_request_fn) { return __async(this, null, function* () { const requestId = id || Math.floor(Math.random() * 1e6).toString(); const request = this.iface.buildRequest(requestId, method, params); const reply = yield this.iface.makeRPCCall(request, long_timeout); if (reply.error) { throw new Error(`RPC Error ${reply.error.code}: ${reply.error.message}`); } if (reply.result && typeof reply.result === "object" && "http_request" in reply.result) { if (!http_request_fn) { throw new Error("HTTP request function not provided"); } const httpRequest = reply.result["http_request"]; const httpResponse = yield http_request_fn(httpRequest["params"]); return this._jadeRpc( httpRequest["on-reply"], httpResponse["body"], void 0, long_timeout, http_request_fn ); } return reply.result; }); } cleanReset() { return __async(this, null, function* () { return this._jadeRpc("debug_clean_reset"); }); } ping() { return __async(this, null, function* () { return this._jadeRpc("ping"); }); } getVersionInfo(nonblocking = false) { return __async(this, null, function* () { const params = nonblocking ? { nonblocking: true } : void 0; return this._jadeRpc("get_version_info", params); }); } setMnemonic(mnemonic, passphrase, temporaryWallet = false) { return __async(this, null, function* () { const params = { mnemonic, temporary_wallet: temporaryWallet }; if (passphrase !== void 0) { params.passphrase = passphrase; } return this._jadeRpc("debug_set_mnemonic", params); }); } authUser(network, http_request_fn, epoch) { return __async(this, null, function* () { if (typeof network !== "string" || network.length === 0) { throw new Error('authUser: "network" must be a non-empty string'); } const computedEpoch = epoch !== void 0 ? epoch : Math.floor(Date.now() / 1e3); const params = { network, epoch: computedEpoch }; return this._jadeRpc("auth_user", params, void 0, true, http_request_fn); }); } addEntropy(entropy) { return __async(this, null, function* () { const params = { entropy }; return this._jadeRpc("add_entropy", params); }); } logout() { return __async(this, null, function* () { return this._jadeRpc("logout"); }); } getXpub(network, path) { return __async(this, null, function* () { const params = { network, path }; return this._jadeRpc("get_xpub", params); }); } setEpoch(epoch) { return __async(this, null, function* () { const now = Math.floor(Date.now() / 1e3); const params = { epoch: epoch !== void 0 ? epoch : now }; return this._jadeRpc("set_epoch", params); }); } registerMultisig(network, multisigName, descriptor) { return __async(this, null, function* () { let mname = multisigName; if (mname === void 0) { mname = "jade" + randomBytes(4).toString("hex"); } const params = { network, multisig_name: mname, descriptor }; return this._jadeRpc("register_multisig", params, void 0, true); }); } getRegisteredMultisigs() { return __async(this, null, function* () { return this._jadeRpc("get_registered_multisigs"); }); } getMultiSigName(network, target) { return __async(this, null, function* () { const summaries = yield this.getRegisteredMultisigs(); for (const [name, sum] of Object.entries(summaries)) { if (sum.variant !== target.variant || sum.sorted !== target.sorted || sum.threshold !== target.threshold || sum.num_signers !== target.signers.length) { continue; } const full = yield this.getRegisteredMultisig(name, false); const desc = full.descriptor; const normalize = (o) => new Uint8Array(Object.values(o.fingerprint)); const match = desc.signers.length === target.signers.length && desc.signers.every((s, i) => { const t = target.signers[i]; const sf = normalize(s); const tf = t.fingerprint; if (sf.length !== tf.length || sf.some((b, idx) => b !== tf[idx])) return false; if (s.xpub !== t.xpub) return false; if (s.derivation.length !== t.derivation.length || s.derivation.some((v, idx) => v !== t.derivation[idx])) return false; return true; }); if (match) { return name; } } return void 0; }); } getRegisteredMultisig(name, asFile = false) { return __async(this, null, function* () { const params = { "multisig_name": name, "as_file": asFile }; return this._jadeRpc("get_registered_multisig", params); }); } getReceiveAddress(network, opts) { return __async(this, null, function* () { const params = { network }; if (opts.path) params.path = opts.path; if (opts.paths) params.paths = opts.paths; if (opts.multisigName) params.multisig_name = opts.multisigName; if (opts.descriptorName) params.descriptor_name = opts.descriptorName; if (opts.variant) params.variant = opts.variant; if (opts.recoveryXpub) params.recovery_xpub = opts.recoveryXpub; if (opts.csvBlocks) params.csv_blocks = opts.csvBlocks; if (opts.confidential) params.confidential = opts.confidential; return this._jadeRpc("get_receive_address", params); }); } signMessage(path, message, useAeSignatures, aeHostCommitment, aeHostEntropy) { return __async(this, null, function* () { if (useAeSignatures) { throw new Error("ae sig not implemented"); } else { const params = { "path": path, "message": message }; return this._jadeRpc("sign_message", params, void 0, true); } }); } signPSBT(network, psbt) { return __async(this, null, function* () { const params = { "network": network, "psbt": psbt }; return this._jadeRpc("sign_psbt", params, void 0, true); }); } getMasterFingerPrint(network) { return __async(this, null, function* () { const xpub = yield this.getXpub(network, []); return getFingerprintFromXpub(xpub, network); }); } }; export { Jade, JadeInterface, SerialTransport, TCPTransport, base64ToBytes, bytesToBase64, getFingerprintFromXpub, hexToBytes }; //# sourceMappingURL=index.mjs.map