Files
meshcore-simple-sensor/flasher/lib/vanity-key-generator.js
UA1ZBE 8d4dd4965d
Some checks failed
Build and deploy Docs site to GitHub Pages / github-pages (push) Has been cancelled
feat: add configurator (Web Serial config via AT commands)
2026-06-05 10:00:14 +03:00

119 lines
3.4 KiB
JavaScript

export class VanityKeyGenerator {
constructor() {
this.workers = [];
this.running = false;
this._resolve = null;
this._reject = null;
this._totalAttempts = 0;
this.onProgress = null;
}
static get numCores() {
return navigator.hardwareConcurrency || 4;
}
/**
* Estimate time for a given hex prefix length
* @param {number} prefixLen - number of hex chars
* @param {number} keysPerSec - estimated throughput
* @returns {string} human-readable estimate
*/
static estimateTime(prefixLen, keysPerSec) {
if (prefixLen === 0) return 'instant';
const expected = Math.pow(16, prefixLen);
const seconds = expected / keysPerSec;
if (seconds < 1) return 'less than a second';
if (seconds < 60) return `~${Math.ceil(seconds)} seconds`;
if (seconds < 3600) return `~${Math.ceil(seconds / 60)} minutes`;
if (seconds < 86400) return `~${Math.ceil(seconds / 3600)} hours`;
return `~${Math.ceil(seconds / 86400)} days`;
}
get attempts() {
return this._totalAttempts;
}
/**
* Start generating a vanity key
* @param {string} prefix - hex prefix to match (1-6 chars)
* @returns {Promise<{ privKey: string, pubKey: string, attempts: number } | null>}
*/
generate(prefix) {
if (this.running) throw new Error('Already running');
prefix = prefix.replace(/[^0-9a-fA-F]/g, '');
if (prefix.length === 0 || prefix.length > 6) {
throw new Error('Prefix must be 1-6 hex characters');
}
this.running = true;
this._totalAttempts = 0;
const numWorkers = VanityKeyGenerator.numCores;
return new Promise((resolve, reject) => {
this._resolve = resolve;
this._reject = reject;
for (let i = 0; i < numWorkers; i++) {
const worker = new Worker(
new URL('./vanity-key-worker.js', import.meta.url),
{ type: 'module' }
);
worker.onmessage = (e) => {
if (!this.running) return;
const data = e.data;
if (data.type === 'progress') {
this._totalAttempts += data.attempts;
if (this.onProgress) this.onProgress(this._totalAttempts);
} else if (data.type === 'match') {
this._totalAttempts += data.attempts;
const result = {
privKey: data.privKey,
pubKey: data.pubKey,
attempts: this._totalAttempts,
};
this._stopWorkers();
this.running = false;
resolve(result);
} else if (data.type === 'error') {
this._stopWorkers();
this.running = false;
reject(new Error(data.message));
} else if (data.type === 'stopped') {
this._totalAttempts += data.attempts;
}
};
worker.onerror = (err) => {
this._stopWorkers();
this.running = false;
reject(new Error(err.message || 'Worker error'));
};
worker.postMessage({ type: 'start', prefix, progressInterval: 200 });
this.workers.push(worker);
}
});
}
_stopWorkers() {
for (const worker of this.workers) {
try { worker.postMessage({ type: 'stop' }); } catch (e) {}
setTimeout(() => worker.terminate(), 500);
}
this.workers = [];
}
cancel() {
const reject = this._reject;
this._stopWorkers();
this.running = false;
if (reject) reject(new Error('Cancelled'));
this._resolve = null;
this._reject = null;
}
}