mirror of
https://github.com/jkingsman/Remote-Terminal-for-MeshCore.git
synced 2026-03-28 17:43:05 +01:00
Update CLAUDE.mds and fold in meshcore cracker lib
This commit is contained in:
@@ -15,7 +15,7 @@ A web interface for MeshCore mesh radio networks. The backend connects to a Mesh
|
||||
│ │ StatusBar│ │ Sidebar │ │MessageList│ │ MessageInput │ │
|
||||
│ └──────────┘ └──────────┘ └──────────┘ └──────────────────┘ │
|
||||
│ ┌────────────────────────────────────────────────────────────┐ │
|
||||
│ │ RawPacketList + CrackerPanel (WebGPU key bruteforcing) │ │
|
||||
│ │ CrackerPanel (global collapsible, WebGPU cracking) │ │
|
||||
│ └────────────────────────────────────────────────────────────┘ │
|
||||
│ │ │
|
||||
│ useWebSocket ←──── Real-time updates │
|
||||
|
||||
@@ -11,6 +11,7 @@ This document provides context for AI assistants and developers working on the R
|
||||
- **Sonner** - Toast notifications
|
||||
- **shadcn/ui components** - Sheet, Tabs, Button (in `components/ui/`)
|
||||
- **meshcore-cracker** - WebGPU-accelerated channel key bruteforcing
|
||||
- **nosleep.js** - Prevents device sleep during cracking
|
||||
|
||||
## Directory Structure
|
||||
|
||||
@@ -448,6 +449,22 @@ npm run build
|
||||
# Then run backend: uv run uvicorn app.main:app --host 0.0.0.0 --port 8000
|
||||
```
|
||||
|
||||
## URL Hash Navigation
|
||||
|
||||
Deep linking to conversations via URL hash:
|
||||
|
||||
- `#channel/RoomName` - Opens a channel (leading `#` stripped from name for cleaner URLs)
|
||||
- `#contact/ContactName` - Opens a DM
|
||||
- `#raw` - Opens the raw packet feed
|
||||
|
||||
```typescript
|
||||
// Parse hash on initial load
|
||||
const hashConv = parseHashConversation();
|
||||
|
||||
// Update hash when conversation changes (uses replaceState to avoid history pollution)
|
||||
window.history.replaceState(null, '', newHash);
|
||||
```
|
||||
|
||||
## CrackerPanel
|
||||
|
||||
The `CrackerPanel` component provides WebGPU-accelerated brute-forcing of channel keys for undecrypted GROUP_TEXT packets.
|
||||
@@ -460,6 +477,8 @@ The `CrackerPanel` component provides WebGPU-accelerated brute-forcing of channe
|
||||
- **Auto-channel creation**: Cracked channels are automatically added to the channel list
|
||||
- **Configurable max length**: Adjustable while running (default: 6)
|
||||
- **Retry failed**: Option to retry failed packets at increasing lengths
|
||||
- **NoSleep integration**: Prevents device sleep during cracking via `nosleep.js`
|
||||
- **Global collapsible panel**: Toggle from sidebar, runs in background when hidden
|
||||
|
||||
### Key Implementation Patterns
|
||||
|
||||
@@ -475,6 +494,12 @@ const maxLengthRef = useRef(6);
|
||||
|
||||
Progress reporting shows rate in Mkeys/s or Gkeys/s depending on speed.
|
||||
|
||||
## Sidebar Features
|
||||
|
||||
- **Sort toggle**: Default is 'recent' (most recent message first), can toggle to alphabetical
|
||||
- **Mark all as read**: Button appears when there are unread messages, clears all unread counts
|
||||
- **Cracker toggle**: Shows/hides the global cracker panel with running status indicator
|
||||
|
||||
## Toast Notifications
|
||||
|
||||
The app uses Sonner for toast notifications via a custom wrapper at `components/ui/sonner.tsx`:
|
||||
|
||||
220
frontend/lib/meshcore-cracker/README.md
Normal file
220
frontend/lib/meshcore-cracker/README.md
Normal file
@@ -0,0 +1,220 @@
|
||||
# MeshCore Cracker
|
||||
|
||||
Standalone library for cracking MeshCore GroupText packets using WebGPU-accelerated brute force.
|
||||
|
||||
## Features
|
||||
|
||||
- WebGPU-accelerated brute force (100M+ keys/second on modern GPUs)
|
||||
- Dictionary attack support with external wordlist
|
||||
- Configurable timestamp and UTF-8 filters
|
||||
- Progress callbacks with ETA
|
||||
- Resume support for interrupted searches
|
||||
- Clean ESM API
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
npm install meshcore-cracker
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
### Basic Usage
|
||||
|
||||
```typescript
|
||||
import { GroupTextCracker } from 'meshcore-cracker';
|
||||
|
||||
const cracker = new GroupTextCracker();
|
||||
|
||||
const result = await cracker.crack(packetHex, {
|
||||
maxLength: 6,
|
||||
});
|
||||
|
||||
if (result.found) {
|
||||
console.log(`Room: #${result.roomName}`);
|
||||
console.log(`Key: ${result.key}`);
|
||||
console.log(`Message: ${result.decryptedMessage}`);
|
||||
}
|
||||
|
||||
cracker.destroy();
|
||||
```
|
||||
|
||||
### With Progress Callback
|
||||
|
||||
```typescript
|
||||
const result = await cracker.crack(packetHex, {
|
||||
maxLength: 8,
|
||||
useTimestampFilter: true,
|
||||
useUtf8Filter: true,
|
||||
}, (progress) => {
|
||||
console.log(`Progress: ${progress.percent.toFixed(1)}%`);
|
||||
console.log(`Rate: ${(progress.rateKeysPerSec / 1e6).toFixed(2)} Mkeys/s`);
|
||||
console.log(`ETA: ${progress.etaSeconds.toFixed(0)}s`);
|
||||
console.log(`Phase: ${progress.phase}`);
|
||||
});
|
||||
```
|
||||
|
||||
### With Dictionary Attack
|
||||
|
||||
```typescript
|
||||
const cracker = new GroupTextCracker();
|
||||
|
||||
// Load wordlist from URL
|
||||
await cracker.loadWordlist('/words_alpha.txt');
|
||||
|
||||
// Or set wordlist directly
|
||||
cracker.setWordlist(['test', 'hello', 'world']);
|
||||
|
||||
const result = await cracker.crack(packetHex, { maxLength: 6 });
|
||||
```
|
||||
|
||||
### Aborting and Resuming
|
||||
|
||||
```typescript
|
||||
const cracker = new GroupTextCracker();
|
||||
|
||||
// Start cracking (in background)
|
||||
const crackPromise = cracker.crack(packetHex, { maxLength: 8 }, (progress) => {
|
||||
// Abort after 10 seconds
|
||||
if (progress.elapsedSeconds > 10) {
|
||||
cracker.abort();
|
||||
}
|
||||
});
|
||||
|
||||
const result = await crackPromise;
|
||||
|
||||
if (result.aborted && result.resumeFrom) {
|
||||
// Resume later from where we left off
|
||||
const resumed = await cracker.crack(packetHex, {
|
||||
maxLength: 8,
|
||||
startFrom: result.resumeFrom,
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### GroupTextCracker
|
||||
|
||||
Main class for cracking GroupText packets.
|
||||
|
||||
#### Methods
|
||||
|
||||
##### `crack(packetHex, options?, onProgress?): Promise<CrackResult>`
|
||||
|
||||
Crack a GroupText packet to find the room name and decrypt the message.
|
||||
|
||||
**Parameters:**
|
||||
- `packetHex: string` - The packet data as a hex string
|
||||
- `options?: CrackOptions` - Cracking options
|
||||
- `onProgress?: ProgressCallback` - Optional progress callback
|
||||
|
||||
**Returns:** `Promise<CrackResult>`
|
||||
|
||||
##### `loadWordlist(url: string): Promise<void>`
|
||||
|
||||
Load a wordlist from a URL for dictionary attacks.
|
||||
|
||||
##### `setWordlist(words: string[]): void`
|
||||
|
||||
Set the wordlist directly from an array.
|
||||
|
||||
##### `abort(): void`
|
||||
|
||||
Abort the current cracking operation.
|
||||
|
||||
##### `isGpuAvailable(): boolean`
|
||||
|
||||
Check if WebGPU is available.
|
||||
|
||||
##### `destroy(): void`
|
||||
|
||||
Clean up GPU resources.
|
||||
|
||||
### CrackOptions
|
||||
|
||||
```typescript
|
||||
interface CrackOptions {
|
||||
maxLength?: number; // Max room name length (default: 8)
|
||||
useTimestampFilter?: boolean; // Filter old timestamps (default: true)
|
||||
useUtf8Filter?: boolean; // Filter invalid UTF-8 (default: true)
|
||||
startFrom?: string; // Resume from position
|
||||
}
|
||||
```
|
||||
|
||||
### CrackResult
|
||||
|
||||
```typescript
|
||||
interface CrackResult {
|
||||
found: boolean;
|
||||
roomName?: string; // Room name without '#'
|
||||
key?: string; // Encryption key (hex)
|
||||
decryptedMessage?: string; // Decrypted message
|
||||
aborted?: boolean; // Was operation aborted
|
||||
resumeFrom?: string; // Position for resume
|
||||
error?: string; // Error message
|
||||
}
|
||||
```
|
||||
|
||||
### ProgressReport
|
||||
|
||||
```typescript
|
||||
interface ProgressReport {
|
||||
checked: number; // Candidates checked
|
||||
total: number; // Total candidates
|
||||
percent: number; // Progress 0-100
|
||||
rateKeysPerSec: number; // Current rate
|
||||
etaSeconds: number; // Estimated time remaining
|
||||
elapsedSeconds: number; // Time elapsed
|
||||
currentLength: number; // Current room name length
|
||||
currentPosition: string; // Current position
|
||||
phase: 'public-key' | 'wordlist' | 'bruteforce';
|
||||
}
|
||||
```
|
||||
|
||||
## Utility Functions
|
||||
|
||||
For advanced usage, the library also exports utility functions:
|
||||
|
||||
```typescript
|
||||
import {
|
||||
deriveKeyFromRoomName, // Derive key from room name
|
||||
getChannelHash, // Get channel hash from key
|
||||
verifyMac, // Verify MAC
|
||||
isTimestampValid, // Check timestamp validity
|
||||
isValidUtf8, // Check UTF-8 validity
|
||||
indexToRoomName, // Convert index to room name
|
||||
roomNameToIndex, // Convert room name to index
|
||||
countNamesForLength, // Count names for a length
|
||||
isWebGpuSupported, // Check WebGPU support
|
||||
PUBLIC_ROOM_NAME, // "[[public room]]"
|
||||
PUBLIC_KEY, // Public room key
|
||||
} from 'meshcore-cracker';
|
||||
```
|
||||
|
||||
## Browser Requirements
|
||||
|
||||
- WebGPU support (Chrome 113+, Edge 113+, or other Chromium-based browsers)
|
||||
- Falls back gracefully with an error if WebGPU is not available
|
||||
|
||||
## Performance
|
||||
|
||||
Typical performance on modern hardware:
|
||||
- **GPU (RTX 3080)**: ~500M keys/second
|
||||
- **GPU (integrated)**: ~50M keys/second
|
||||
|
||||
Search space by room name length:
|
||||
| Length | Candidates | Time @ 100M/s |
|
||||
|--------|------------|---------------|
|
||||
| 1 | 36 | instant |
|
||||
| 2 | 1,296 | instant |
|
||||
| 3 | 47,952 | instant |
|
||||
| 4 | 1,774,224 | <1s |
|
||||
| 5 | 65,646,288 | <1s |
|
||||
| 6 | 2,428,912,656 | ~24s |
|
||||
| 7 | 89,869,768,272 | ~15min |
|
||||
| 8 | 3,325,181,426,064 | ~9h |
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
||||
15
frontend/lib/meshcore-cracker/build.js
Normal file
15
frontend/lib/meshcore-cracker/build.js
Normal file
@@ -0,0 +1,15 @@
|
||||
import * as esbuild from 'esbuild';
|
||||
|
||||
await esbuild.build({
|
||||
entryPoints: ['src/index.ts'],
|
||||
bundle: true,
|
||||
format: 'esm',
|
||||
outfile: 'dist/index.js',
|
||||
sourcemap: true,
|
||||
target: 'es2020',
|
||||
platform: 'browser',
|
||||
external: [],
|
||||
minify: false,
|
||||
});
|
||||
|
||||
console.log('Build complete: dist/index.js');
|
||||
65
frontend/lib/meshcore-cracker/dist/core.d.ts
vendored
Normal file
65
frontend/lib/meshcore-cracker/dist/core.d.ts
vendored
Normal file
@@ -0,0 +1,65 @@
|
||||
export declare const CHARS = "abcdefghijklmnopqrstuvwxyz0123456789";
|
||||
export declare const CHARS_LEN: number;
|
||||
export declare const CHARS_WITH_DASH: string;
|
||||
export declare const PUBLIC_ROOM_NAME = "[[public room]]";
|
||||
export declare const PUBLIC_KEY = "8b3387e9c5cdea6ac9e5edbaa115cd72";
|
||||
/**
|
||||
* Convert room name to (length, index) for resuming/skipping.
|
||||
* Index encoding: LSB-first (first character = least significant digit).
|
||||
*/
|
||||
export declare function roomNameToIndex(name: string): {
|
||||
length: number;
|
||||
index: number;
|
||||
} | null;
|
||||
/**
|
||||
* Convert (length, index) to room name.
|
||||
* Index encoding: LSB-first (first character = least significant digit).
|
||||
*/
|
||||
export declare function indexToRoomName(length: number, idx: number): string | null;
|
||||
/**
|
||||
* Derive 128-bit key from room name using SHA256.
|
||||
* Room names are prefixed with '#' before hashing.
|
||||
*/
|
||||
export declare function deriveKeyFromRoomName(roomName: string): string;
|
||||
/**
|
||||
* Compute channel hash (first byte of SHA256(key)).
|
||||
*/
|
||||
export declare function getChannelHash(keyHex: string): string;
|
||||
/**
|
||||
* Verify MAC using HMAC-SHA256 with 32-byte padded key.
|
||||
*/
|
||||
export declare function verifyMac(ciphertext: string, cipherMac: string, keyHex: string): boolean;
|
||||
/**
|
||||
* Count valid room names for a given length.
|
||||
* Accounts for dash rules (no start/end dash, no consecutive dashes).
|
||||
*/
|
||||
export declare function countNamesForLength(len: number): number;
|
||||
/**
|
||||
* Check if timestamp is within last month.
|
||||
*/
|
||||
export declare function isTimestampValid(timestamp: number, now?: number): boolean;
|
||||
/**
|
||||
* Check for valid UTF-8 (no replacement characters).
|
||||
*/
|
||||
export declare function isValidUtf8(text: string): boolean;
|
||||
/**
|
||||
* Room name generator - iterates through all valid room names.
|
||||
*/
|
||||
export declare class RoomNameGenerator {
|
||||
private length;
|
||||
private indices;
|
||||
private done;
|
||||
private currentInLength;
|
||||
private totalForLength;
|
||||
current(): string;
|
||||
getLength(): number;
|
||||
getCurrentInLength(): number;
|
||||
getTotalForLength(): number;
|
||||
getRemainingInLength(): number;
|
||||
isDone(): boolean;
|
||||
next(): boolean;
|
||||
private isValid;
|
||||
nextValid(): boolean;
|
||||
skipTo(targetLength: number, targetIndex: number): void;
|
||||
}
|
||||
//# sourceMappingURL=core.d.ts.map
|
||||
1
frontend/lib/meshcore-cracker/dist/core.d.ts.map
vendored
Normal file
1
frontend/lib/meshcore-cracker/dist/core.d.ts.map
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"core.d.ts","sourceRoot":"","sources":["../src/core.ts"],"names":[],"mappings":"AAOA,eAAO,MAAM,KAAK,yCAAyC,CAAC;AAC5D,eAAO,MAAM,SAAS,QAAe,CAAC;AACtC,eAAO,MAAM,eAAe,QAAc,CAAC;AAG3C,eAAO,MAAM,gBAAgB,oBAAoB,CAAC;AAClD,eAAO,MAAM,UAAU,qCAAqC,CAAC;AAE7D;;;GAGG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CA+BtF;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CA0B1E;AAED;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAM9D;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAGrD;AAED;;GAEG;AACH,wBAAgB,SAAS,CAAC,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAKxF;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAwBvD;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,OAAO,CAIzE;AAED;;GAEG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAEjD;AAED;;GAEG;AACH,qBAAa,iBAAiB;IAC5B,OAAO,CAAC,MAAM,CAAK;IACnB,OAAO,CAAC,OAAO,CAAiB;IAChC,OAAO,CAAC,IAAI,CAAS;IACrB,OAAO,CAAC,eAAe,CAAK;IAC5B,OAAO,CAAC,cAAc,CAAa;IAEnC,OAAO,IAAI,MAAM;IAIjB,SAAS,IAAI,MAAM;IAInB,kBAAkB,IAAI,MAAM;IAI5B,iBAAiB,IAAI,MAAM;IAI3B,oBAAoB,IAAI,MAAM;IAI9B,MAAM,IAAI,OAAO;IAIjB,IAAI,IAAI,OAAO;IA6Cf,OAAO,CAAC,OAAO;IAcf,SAAS,IAAI,OAAO;IAWpB,MAAM,CAAC,YAAY,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,IAAI;CAiBxD"}
|
||||
240
frontend/lib/meshcore-cracker/dist/core.js
vendored
Normal file
240
frontend/lib/meshcore-cracker/dist/core.js
vendored
Normal file
@@ -0,0 +1,240 @@
|
||||
// Core logic for MeshCore packet cracker - pure functions
|
||||
import SHA256 from 'crypto-js/sha256';
|
||||
import HmacSHA256 from 'crypto-js/hmac-sha256';
|
||||
import Hex from 'crypto-js/enc-hex';
|
||||
// Room name character set
|
||||
export const CHARS = 'abcdefghijklmnopqrstuvwxyz0123456789';
|
||||
export const CHARS_LEN = CHARS.length; // 36
|
||||
export const CHARS_WITH_DASH = CHARS + '-';
|
||||
// Public room special case
|
||||
export const PUBLIC_ROOM_NAME = '[[public room]]';
|
||||
export const PUBLIC_KEY = '8b3387e9c5cdea6ac9e5edbaa115cd72';
|
||||
/**
|
||||
* Convert room name to (length, index) for resuming/skipping.
|
||||
* Index encoding: LSB-first (first character = least significant digit).
|
||||
*/
|
||||
export function roomNameToIndex(name) {
|
||||
if (!name || name.length === 0) {
|
||||
return null;
|
||||
}
|
||||
const length = name.length;
|
||||
let index = 0;
|
||||
let multiplier = 1;
|
||||
// Process from left to right (first char is LSB, matching indexToRoomName)
|
||||
for (let i = 0; i < length; i++) {
|
||||
const c = name[i];
|
||||
const charIdx = CHARS_WITH_DASH.indexOf(c);
|
||||
if (charIdx === -1) {
|
||||
return null;
|
||||
} // Invalid character
|
||||
const isFirst = i === 0;
|
||||
const isLast = i === length - 1;
|
||||
const charCount = isFirst || isLast ? 36 : 37;
|
||||
// Dash not allowed at start/end
|
||||
if ((isFirst || isLast) && charIdx === 36) {
|
||||
return null;
|
||||
}
|
||||
index += charIdx * multiplier;
|
||||
multiplier *= charCount;
|
||||
}
|
||||
return { length, index };
|
||||
}
|
||||
/**
|
||||
* Convert (length, index) to room name.
|
||||
* Index encoding: LSB-first (first character = least significant digit).
|
||||
*/
|
||||
export function indexToRoomName(length, idx) {
|
||||
if (length <= 0) {
|
||||
return null;
|
||||
}
|
||||
let result = '';
|
||||
let remaining = idx;
|
||||
let prevWasDash = false;
|
||||
for (let i = 0; i < length; i++) {
|
||||
const isFirst = i === 0;
|
||||
const isLast = i === length - 1;
|
||||
const charCount = isFirst || isLast ? 36 : 37;
|
||||
const charIdx = remaining % charCount;
|
||||
remaining = Math.floor(remaining / charCount);
|
||||
const isDash = charIdx === 36;
|
||||
if (isDash && prevWasDash) {
|
||||
return null;
|
||||
} // Invalid: consecutive dashes
|
||||
prevWasDash = isDash;
|
||||
result += CHARS_WITH_DASH[charIdx];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
/**
|
||||
* Derive 128-bit key from room name using SHA256.
|
||||
* Room names are prefixed with '#' before hashing.
|
||||
*/
|
||||
export function deriveKeyFromRoomName(roomName) {
|
||||
if (roomName === PUBLIC_ROOM_NAME) {
|
||||
return PUBLIC_KEY;
|
||||
}
|
||||
const hash = SHA256(roomName);
|
||||
return hash.toString(Hex).substring(0, 32);
|
||||
}
|
||||
/**
|
||||
* Compute channel hash (first byte of SHA256(key)).
|
||||
*/
|
||||
export function getChannelHash(keyHex) {
|
||||
const hash = SHA256(Hex.parse(keyHex));
|
||||
return hash.toString(Hex).substring(0, 2);
|
||||
}
|
||||
/**
|
||||
* Verify MAC using HMAC-SHA256 with 32-byte padded key.
|
||||
*/
|
||||
export function verifyMac(ciphertext, cipherMac, keyHex) {
|
||||
const paddedKey = keyHex.padEnd(64, '0');
|
||||
const hmac = HmacSHA256(Hex.parse(ciphertext), Hex.parse(paddedKey));
|
||||
const computed = hmac.toString(Hex).substring(0, 4).toLowerCase();
|
||||
return computed === cipherMac.toLowerCase();
|
||||
}
|
||||
/**
|
||||
* Count valid room names for a given length.
|
||||
* Accounts for dash rules (no start/end dash, no consecutive dashes).
|
||||
*/
|
||||
export function countNamesForLength(len) {
|
||||
if (len === 1) {
|
||||
return CHARS_LEN;
|
||||
}
|
||||
if (len === 2) {
|
||||
return CHARS_LEN * CHARS_LEN;
|
||||
}
|
||||
// For length >= 3: first and last are CHARS (36), middle follows no-consecutive-dash rule
|
||||
// Middle length = len - 2
|
||||
// Use DP: count sequences of length k with no consecutive dashes
|
||||
// endsWithNonDash[k], endsWithDash[k]
|
||||
let endsNonDash = CHARS_LEN; // length 1 middle
|
||||
let endsDash = 1;
|
||||
for (let i = 2; i <= len - 2; i++) {
|
||||
const newEndsNonDash = (endsNonDash + endsDash) * CHARS_LEN;
|
||||
const newEndsDash = endsNonDash; // dash can only follow non-dash
|
||||
endsNonDash = newEndsNonDash;
|
||||
endsDash = newEndsDash;
|
||||
}
|
||||
const middleCount = len > 2 ? endsNonDash + endsDash : 1;
|
||||
return CHARS_LEN * middleCount * CHARS_LEN;
|
||||
}
|
||||
/**
|
||||
* Check if timestamp is within last month.
|
||||
*/
|
||||
export function isTimestampValid(timestamp, now) {
|
||||
const ONE_MONTH_SECONDS = 30 * 24 * 60 * 60;
|
||||
const currentTime = now ?? Math.floor(Date.now() / 1000);
|
||||
return timestamp <= currentTime && timestamp >= currentTime - ONE_MONTH_SECONDS;
|
||||
}
|
||||
/**
|
||||
* Check for valid UTF-8 (no replacement characters).
|
||||
*/
|
||||
export function isValidUtf8(text) {
|
||||
return !text.includes('\uFFFD');
|
||||
}
|
||||
/**
|
||||
* Room name generator - iterates through all valid room names.
|
||||
*/
|
||||
export class RoomNameGenerator {
|
||||
constructor() {
|
||||
this.length = 1;
|
||||
this.indices = [0];
|
||||
this.done = false;
|
||||
this.currentInLength = 0;
|
||||
this.totalForLength = CHARS_LEN;
|
||||
}
|
||||
current() {
|
||||
return this.indices.map((i) => (i === CHARS_LEN ? '-' : CHARS[i])).join('');
|
||||
}
|
||||
getLength() {
|
||||
return this.length;
|
||||
}
|
||||
getCurrentInLength() {
|
||||
return this.currentInLength;
|
||||
}
|
||||
getTotalForLength() {
|
||||
return this.totalForLength;
|
||||
}
|
||||
getRemainingInLength() {
|
||||
return this.totalForLength - this.currentInLength;
|
||||
}
|
||||
isDone() {
|
||||
return this.done;
|
||||
}
|
||||
next() {
|
||||
if (this.done) {
|
||||
return false;
|
||||
}
|
||||
this.currentInLength++;
|
||||
// Increment with carry, respecting dash rules
|
||||
let pos = this.length - 1;
|
||||
while (pos >= 0) {
|
||||
const isFirst = pos === 0;
|
||||
const isLast = pos === this.length - 1;
|
||||
const maxVal = isFirst || isLast ? CHARS_LEN - 1 : CHARS_LEN; // CHARS_LEN = dash index
|
||||
if (this.indices[pos] < maxVal) {
|
||||
this.indices[pos]++;
|
||||
// Check dash rule: no consecutive dashes
|
||||
if (this.indices[pos] === CHARS_LEN && pos > 0 && this.indices[pos - 1] === CHARS_LEN) {
|
||||
// Would create consecutive dashes, continue incrementing
|
||||
continue;
|
||||
}
|
||||
// Reset all positions after this one
|
||||
for (let i = pos + 1; i < this.length; i++) {
|
||||
this.indices[i] = 0;
|
||||
}
|
||||
// Validate: check no consecutive dashes in reset portion
|
||||
if (this.isValid()) {
|
||||
return true;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
pos--;
|
||||
}
|
||||
// Overflow - increase length
|
||||
this.length++;
|
||||
this.indices = new Array(this.length).fill(0);
|
||||
this.currentInLength = 0;
|
||||
this.totalForLength = countNamesForLength(this.length);
|
||||
return true;
|
||||
}
|
||||
isValid() {
|
||||
for (let i = 0; i < this.length; i++) {
|
||||
const isDash = this.indices[i] === CHARS_LEN;
|
||||
if (isDash && (i === 0 || i === this.length - 1)) {
|
||||
return false;
|
||||
}
|
||||
if (isDash && i > 0 && this.indices[i - 1] === CHARS_LEN) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
// Skip invalid combinations efficiently
|
||||
nextValid() {
|
||||
do {
|
||||
if (!this.next()) {
|
||||
return false;
|
||||
}
|
||||
} while (!this.isValid());
|
||||
return true;
|
||||
}
|
||||
// Skip to a specific (length, index) position
|
||||
// Index encoding: first char is LSB (consistent with indexToRoomName)
|
||||
skipTo(targetLength, targetIndex) {
|
||||
this.length = targetLength;
|
||||
this.indices = new Array(targetLength).fill(0);
|
||||
this.totalForLength = countNamesForLength(targetLength);
|
||||
// Convert index to indices array (LSB first = position 0)
|
||||
let remaining = targetIndex;
|
||||
for (let i = 0; i < targetLength; i++) {
|
||||
const isFirst = i === 0;
|
||||
const isLast = i === targetLength - 1;
|
||||
const charCount = isFirst || isLast ? CHARS_LEN : CHARS_LEN + 1;
|
||||
this.indices[i] = remaining % charCount;
|
||||
remaining = Math.floor(remaining / charCount);
|
||||
}
|
||||
this.currentInLength = targetIndex;
|
||||
}
|
||||
}
|
||||
//# sourceMappingURL=core.js.map
|
||||
1
frontend/lib/meshcore-cracker/dist/core.js.map
vendored
Normal file
1
frontend/lib/meshcore-cracker/dist/core.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
61
frontend/lib/meshcore-cracker/dist/cracker.d.ts
vendored
Normal file
61
frontend/lib/meshcore-cracker/dist/cracker.d.ts
vendored
Normal file
@@ -0,0 +1,61 @@
|
||||
/**
|
||||
* GroupTextCracker - Standalone MeshCore GroupText packet cracker
|
||||
*
|
||||
* Cracks encrypted GroupText packets by trying room names until the
|
||||
* correct encryption key is found.
|
||||
*/
|
||||
import type { CrackOptions, CrackResult, ProgressCallback, DecodedPacket } from './types';
|
||||
/**
|
||||
* Main cracker class for MeshCore GroupText packets.
|
||||
*/
|
||||
export declare class GroupTextCracker {
|
||||
private gpuInstance;
|
||||
private wordlist;
|
||||
private abortFlag;
|
||||
private useTimestampFilter;
|
||||
private useUtf8Filter;
|
||||
/**
|
||||
* Load a wordlist from a URL for dictionary attacks.
|
||||
* The wordlist should be a text file with one word per line.
|
||||
*
|
||||
* @param url - URL to fetch the wordlist from
|
||||
*/
|
||||
loadWordlist(url: string): Promise<void>;
|
||||
/**
|
||||
* Set the wordlist directly from an array of words.
|
||||
*
|
||||
* @param words - Array of room names to try
|
||||
*/
|
||||
setWordlist(words: string[]): void;
|
||||
/**
|
||||
* Abort the current cracking operation.
|
||||
* The crack() method will return with aborted: true.
|
||||
*/
|
||||
abort(): void;
|
||||
/**
|
||||
* Check if WebGPU is available in the current environment.
|
||||
*/
|
||||
isGpuAvailable(): boolean;
|
||||
/**
|
||||
* Decode a packet and extract the information needed for cracking.
|
||||
*
|
||||
* @param packetHex - The packet data as a hex string
|
||||
* @returns Decoded packet info or null if not a GroupText packet
|
||||
*/
|
||||
decodePacket(packetHex: string): Promise<DecodedPacket | null>;
|
||||
/**
|
||||
* Crack a GroupText packet to find the room name and decrypt the message.
|
||||
*
|
||||
* @param packetHex - The packet data as a hex string
|
||||
* @param options - Cracking options
|
||||
* @param onProgress - Optional callback for progress updates
|
||||
* @returns The cracking result
|
||||
*/
|
||||
crack(packetHex: string, options?: CrackOptions, onProgress?: ProgressCallback): Promise<CrackResult>;
|
||||
/**
|
||||
* Clean up GPU resources.
|
||||
* Call this when you're done using the cracker.
|
||||
*/
|
||||
destroy(): void;
|
||||
}
|
||||
//# sourceMappingURL=cracker.d.ts.map
|
||||
1
frontend/lib/meshcore-cracker/dist/cracker.d.ts.map
vendored
Normal file
1
frontend/lib/meshcore-cracker/dist/cracker.d.ts.map
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"cracker.d.ts","sourceRoot":"","sources":["../src/cracker.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAgBH,OAAO,KAAK,EAAE,YAAY,EAAE,WAAW,EAAkB,gBAAgB,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAe1G;;GAEG;AACH,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,WAAW,CAA8B;IACjD,OAAO,CAAC,QAAQ,CAAgB;IAChC,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,kBAAkB,CAAQ;IAClC,OAAO,CAAC,aAAa,CAAQ;IAE7B;;;;;OAKG;IACG,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAgB9C;;;;OAIG;IACH,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,IAAI;IAMlC;;;OAGG;IACH,KAAK,IAAI,IAAI;IAIb;;OAEG;IACH,cAAc,IAAI,OAAO;IAIzB;;;;;OAKG;IACG,YAAY,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC;IA8BpE;;;;;;;OAOG;IACG,KAAK,CACT,SAAS,EAAE,MAAM,EACjB,OAAO,CAAC,EAAE,YAAY,EACtB,UAAU,CAAC,EAAE,gBAAgB,GAC5B,OAAO,CAAC,WAAW,CAAC;IA0PvB;;;OAGG;IACH,OAAO,IAAI,IAAI;CAMhB"}
|
||||
327
frontend/lib/meshcore-cracker/dist/cracker.js
vendored
Normal file
327
frontend/lib/meshcore-cracker/dist/cracker.js
vendored
Normal file
@@ -0,0 +1,327 @@
|
||||
/**
|
||||
* GroupTextCracker - Standalone MeshCore GroupText packet cracker
|
||||
*
|
||||
* Cracks encrypted GroupText packets by trying room names until the
|
||||
* correct encryption key is found.
|
||||
*/
|
||||
import { MeshCorePacketDecoder, ChannelCrypto } from '@michaelhart/meshcore-decoder';
|
||||
import { GpuBruteForce, isWebGpuSupported } from './gpu-bruteforce';
|
||||
import { PUBLIC_ROOM_NAME, PUBLIC_KEY, indexToRoomName, roomNameToIndex, deriveKeyFromRoomName, getChannelHash, verifyMac, countNamesForLength, isTimestampValid, isValidUtf8, } from './core';
|
||||
// Valid room name characters (for wordlist filtering)
|
||||
const VALID_CHARS = /^[a-z0-9-]+$/;
|
||||
const NO_DASH_AT_ENDS = /^[a-z0-9].*[a-z0-9]$|^[a-z0-9]$/;
|
||||
const NO_CONSECUTIVE_DASHES = /--/;
|
||||
function isValidRoomName(name) {
|
||||
if (!name || name.length === 0)
|
||||
return false;
|
||||
if (!VALID_CHARS.test(name))
|
||||
return false;
|
||||
if (name.length > 1 && !NO_DASH_AT_ENDS.test(name))
|
||||
return false;
|
||||
if (NO_CONSECUTIVE_DASHES.test(name))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
/**
|
||||
* Main cracker class for MeshCore GroupText packets.
|
||||
*/
|
||||
export class GroupTextCracker {
|
||||
constructor() {
|
||||
this.gpuInstance = null;
|
||||
this.wordlist = [];
|
||||
this.abortFlag = false;
|
||||
this.useTimestampFilter = true;
|
||||
this.useUtf8Filter = true;
|
||||
}
|
||||
/**
|
||||
* Load a wordlist from a URL for dictionary attacks.
|
||||
* The wordlist should be a text file with one word per line.
|
||||
*
|
||||
* @param url - URL to fetch the wordlist from
|
||||
*/
|
||||
async loadWordlist(url) {
|
||||
const response = await fetch(url);
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to load wordlist: ${response.status} ${response.statusText}`);
|
||||
}
|
||||
const text = await response.text();
|
||||
const allWords = text
|
||||
.split('\n')
|
||||
.map((w) => w.trim().toLowerCase())
|
||||
.filter((w) => w.length > 0);
|
||||
// Filter to valid room names only
|
||||
this.wordlist = allWords.filter(isValidRoomName);
|
||||
}
|
||||
/**
|
||||
* Set the wordlist directly from an array of words.
|
||||
*
|
||||
* @param words - Array of room names to try
|
||||
*/
|
||||
setWordlist(words) {
|
||||
this.wordlist = words
|
||||
.map((w) => w.trim().toLowerCase())
|
||||
.filter(isValidRoomName);
|
||||
}
|
||||
/**
|
||||
* Abort the current cracking operation.
|
||||
* The crack() method will return with aborted: true.
|
||||
*/
|
||||
abort() {
|
||||
this.abortFlag = true;
|
||||
}
|
||||
/**
|
||||
* Check if WebGPU is available in the current environment.
|
||||
*/
|
||||
isGpuAvailable() {
|
||||
return isWebGpuSupported();
|
||||
}
|
||||
/**
|
||||
* Decode a packet and extract the information needed for cracking.
|
||||
*
|
||||
* @param packetHex - The packet data as a hex string
|
||||
* @returns Decoded packet info or null if not a GroupText packet
|
||||
*/
|
||||
async decodePacket(packetHex) {
|
||||
const cleanHex = packetHex.trim().replace(/\s+/g, '').replace(/^0x/i, '');
|
||||
if (!cleanHex || !/^[0-9a-fA-F]+$/.test(cleanHex)) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
const decoded = await MeshCorePacketDecoder.decodeWithVerification(cleanHex, {});
|
||||
const payload = decoded.payload?.decoded;
|
||||
if (!payload?.channelHash || !payload?.ciphertext || !payload?.cipherMac) {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
channelHash: payload.channelHash,
|
||||
ciphertext: payload.ciphertext,
|
||||
cipherMac: payload.cipherMac,
|
||||
isGroupText: true,
|
||||
};
|
||||
}
|
||||
catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Crack a GroupText packet to find the room name and decrypt the message.
|
||||
*
|
||||
* @param packetHex - The packet data as a hex string
|
||||
* @param options - Cracking options
|
||||
* @param onProgress - Optional callback for progress updates
|
||||
* @returns The cracking result
|
||||
*/
|
||||
async crack(packetHex, options, onProgress) {
|
||||
this.abortFlag = false;
|
||||
this.useTimestampFilter = options?.useTimestampFilter ?? true;
|
||||
this.useUtf8Filter = options?.useUtf8Filter ?? true;
|
||||
const maxLength = options?.maxLength ?? 8;
|
||||
// Decode packet
|
||||
const decoded = await this.decodePacket(packetHex);
|
||||
if (!decoded) {
|
||||
return { found: false, error: 'Invalid packet or not a GroupText packet' };
|
||||
}
|
||||
const { channelHash, ciphertext, cipherMac } = decoded;
|
||||
const targetHashByte = parseInt(channelHash, 16);
|
||||
// Initialize GPU if not already done
|
||||
if (!this.gpuInstance) {
|
||||
this.gpuInstance = new GpuBruteForce();
|
||||
const gpuOk = await this.gpuInstance.init();
|
||||
if (!gpuOk) {
|
||||
return { found: false, error: 'WebGPU not available' };
|
||||
}
|
||||
}
|
||||
const startTime = performance.now();
|
||||
let totalChecked = 0;
|
||||
let lastProgressUpdate = performance.now();
|
||||
// Determine starting position
|
||||
let startFromLength = 1;
|
||||
let startFromOffset = 0;
|
||||
if (options?.startFrom) {
|
||||
const pos = roomNameToIndex(options.startFrom);
|
||||
if (pos) {
|
||||
startFromLength = pos.length;
|
||||
startFromOffset = pos.index + 1; // Start after the given position
|
||||
if (startFromOffset >= countNamesForLength(startFromLength)) {
|
||||
startFromLength++;
|
||||
startFromOffset = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Calculate total candidates for progress
|
||||
let totalCandidates = 0;
|
||||
for (let l = startFromLength; l <= maxLength; l++) {
|
||||
totalCandidates += countNamesForLength(l);
|
||||
}
|
||||
totalCandidates -= startFromOffset;
|
||||
// Helper to report progress
|
||||
const reportProgress = (phase, currentLength, currentPosition) => {
|
||||
if (!onProgress)
|
||||
return;
|
||||
const now = performance.now();
|
||||
const elapsed = (now - startTime) / 1000;
|
||||
const rate = elapsed > 0 ? Math.round(totalChecked / elapsed) : 0;
|
||||
const remaining = totalCandidates - totalChecked;
|
||||
const eta = rate > 0 ? remaining / rate : 0;
|
||||
onProgress({
|
||||
checked: totalChecked,
|
||||
total: totalCandidates,
|
||||
percent: totalCandidates > 0 ? Math.min(100, (totalChecked / totalCandidates) * 100) : 0,
|
||||
rateKeysPerSec: rate,
|
||||
etaSeconds: eta,
|
||||
elapsedSeconds: elapsed,
|
||||
currentLength,
|
||||
currentPosition,
|
||||
phase,
|
||||
});
|
||||
};
|
||||
// Helper to verify MAC and filters
|
||||
const verifyMacAndFilters = (key) => {
|
||||
if (!verifyMac(ciphertext, cipherMac, key)) {
|
||||
return { valid: false };
|
||||
}
|
||||
const result = ChannelCrypto.decryptGroupTextMessage(ciphertext, cipherMac, key);
|
||||
if (!result.success || !result.data) {
|
||||
return { valid: false };
|
||||
}
|
||||
if (this.useTimestampFilter && !isTimestampValid(result.data.timestamp)) {
|
||||
return { valid: false };
|
||||
}
|
||||
if (this.useUtf8Filter && !isValidUtf8(result.data.message)) {
|
||||
return { valid: false };
|
||||
}
|
||||
return { valid: true, message: result.data.message };
|
||||
};
|
||||
// Phase 1: Try public key
|
||||
if (startFromLength === 1 && startFromOffset === 0) {
|
||||
reportProgress('public-key', 0, PUBLIC_ROOM_NAME);
|
||||
const publicChannelHash = getChannelHash(PUBLIC_KEY);
|
||||
if (channelHash === publicChannelHash) {
|
||||
const result = verifyMacAndFilters(PUBLIC_KEY);
|
||||
if (result.valid) {
|
||||
return {
|
||||
found: true,
|
||||
roomName: PUBLIC_ROOM_NAME,
|
||||
key: PUBLIC_KEY,
|
||||
decryptedMessage: result.message,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
// Phase 2: Dictionary attack
|
||||
if (this.wordlist.length > 0 && startFromLength === 1 && startFromOffset === 0) {
|
||||
for (let i = 0; i < this.wordlist.length; i++) {
|
||||
if (this.abortFlag) {
|
||||
return {
|
||||
found: false,
|
||||
aborted: true,
|
||||
resumeFrom: this.wordlist[i],
|
||||
};
|
||||
}
|
||||
const word = this.wordlist[i];
|
||||
const key = deriveKeyFromRoomName('#' + word);
|
||||
const wordChannelHash = getChannelHash(key);
|
||||
if (parseInt(wordChannelHash, 16) === targetHashByte) {
|
||||
const result = verifyMacAndFilters(key);
|
||||
if (result.valid) {
|
||||
return {
|
||||
found: true,
|
||||
roomName: word,
|
||||
key,
|
||||
decryptedMessage: result.message,
|
||||
};
|
||||
}
|
||||
}
|
||||
// Progress update
|
||||
const now = performance.now();
|
||||
if (now - lastProgressUpdate >= 200) {
|
||||
reportProgress('wordlist', word.length, word);
|
||||
lastProgressUpdate = now;
|
||||
await new Promise((resolve) => setTimeout(resolve, 0));
|
||||
}
|
||||
}
|
||||
}
|
||||
// Phase 3: GPU brute force
|
||||
const INITIAL_BATCH_SIZE = 32768;
|
||||
const TARGET_DISPATCH_MS = 1000;
|
||||
let currentBatchSize = INITIAL_BATCH_SIZE;
|
||||
let batchSizeTuned = false;
|
||||
for (let length = startFromLength; length <= maxLength; length++) {
|
||||
if (this.abortFlag) {
|
||||
const resumePos = indexToRoomName(length, 0);
|
||||
return {
|
||||
found: false,
|
||||
aborted: true,
|
||||
resumeFrom: resumePos || undefined,
|
||||
};
|
||||
}
|
||||
const totalForLength = countNamesForLength(length);
|
||||
let offset = length === startFromLength ? startFromOffset : 0;
|
||||
while (offset < totalForLength) {
|
||||
if (this.abortFlag) {
|
||||
const resumePos = indexToRoomName(length, offset);
|
||||
return {
|
||||
found: false,
|
||||
aborted: true,
|
||||
resumeFrom: resumePos || undefined,
|
||||
};
|
||||
}
|
||||
const batchSize = Math.min(currentBatchSize, totalForLength - offset);
|
||||
const dispatchStart = performance.now();
|
||||
const matches = await this.gpuInstance.runBatch(targetHashByte, length, offset, batchSize, ciphertext, cipherMac);
|
||||
const dispatchTime = performance.now() - dispatchStart;
|
||||
totalChecked += batchSize;
|
||||
// Auto-tune batch size
|
||||
if (!batchSizeTuned && batchSize >= INITIAL_BATCH_SIZE && dispatchTime > 0) {
|
||||
const scaleFactor = TARGET_DISPATCH_MS / dispatchTime;
|
||||
const optimalBatchSize = Math.round(batchSize * scaleFactor);
|
||||
const rounded = Math.pow(2, Math.round(Math.log2(Math.max(INITIAL_BATCH_SIZE, optimalBatchSize))));
|
||||
currentBatchSize = Math.max(INITIAL_BATCH_SIZE, rounded);
|
||||
batchSizeTuned = true;
|
||||
}
|
||||
// Check matches
|
||||
for (const matchIdx of matches) {
|
||||
const roomName = indexToRoomName(length, matchIdx);
|
||||
if (!roomName)
|
||||
continue;
|
||||
const key = deriveKeyFromRoomName('#' + roomName);
|
||||
const result = verifyMacAndFilters(key);
|
||||
if (result.valid) {
|
||||
return {
|
||||
found: true,
|
||||
roomName,
|
||||
key,
|
||||
decryptedMessage: result.message,
|
||||
};
|
||||
}
|
||||
}
|
||||
offset += batchSize;
|
||||
// Progress update
|
||||
const now = performance.now();
|
||||
if (now - lastProgressUpdate >= 200) {
|
||||
const currentPos = indexToRoomName(length, Math.min(offset, totalForLength - 1)) || '';
|
||||
reportProgress('bruteforce', length, currentPos);
|
||||
lastProgressUpdate = now;
|
||||
await new Promise((resolve) => setTimeout(resolve, 0));
|
||||
}
|
||||
}
|
||||
}
|
||||
// Not found
|
||||
const lastPos = indexToRoomName(maxLength, countNamesForLength(maxLength) - 1);
|
||||
return {
|
||||
found: false,
|
||||
resumeFrom: lastPos || undefined,
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Clean up GPU resources.
|
||||
* Call this when you're done using the cracker.
|
||||
*/
|
||||
destroy() {
|
||||
if (this.gpuInstance) {
|
||||
this.gpuInstance.destroy();
|
||||
this.gpuInstance = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
//# sourceMappingURL=cracker.js.map
|
||||
1
frontend/lib/meshcore-cracker/dist/cracker.js.map
vendored
Normal file
1
frontend/lib/meshcore-cracker/dist/cracker.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
34
frontend/lib/meshcore-cracker/dist/gpu-bruteforce.d.ts
vendored
Normal file
34
frontend/lib/meshcore-cracker/dist/gpu-bruteforce.d.ts
vendored
Normal file
@@ -0,0 +1,34 @@
|
||||
export interface GpuBruteForceResult {
|
||||
found: boolean;
|
||||
roomName?: string;
|
||||
key?: string;
|
||||
candidateIndices?: number[];
|
||||
}
|
||||
export declare class GpuBruteForce {
|
||||
private device;
|
||||
private pipeline;
|
||||
private bindGroupLayout;
|
||||
private paramsBuffer;
|
||||
private matchCountBuffer;
|
||||
private matchIndicesBuffer;
|
||||
private ciphertextBuffer;
|
||||
private ciphertextBufferSize;
|
||||
private matchCountReadBuffers;
|
||||
private matchIndicesReadBuffers;
|
||||
private currentReadBufferIndex;
|
||||
private bindGroup;
|
||||
private bindGroupDirty;
|
||||
private static readonly ZERO_DATA;
|
||||
private shaderCode;
|
||||
init(): Promise<boolean>;
|
||||
isAvailable(): boolean;
|
||||
indexToRoomName(idx: number, length: number): string | null;
|
||||
countNamesForLength(len: number): number;
|
||||
runBatch(targetChannelHash: number, nameLength: number, batchOffset: number, batchSize: number, ciphertextHex?: string, targetMacHex?: string): Promise<number[]>;
|
||||
destroy(): void;
|
||||
}
|
||||
/**
|
||||
* Check if WebGPU is supported in the current browser.
|
||||
*/
|
||||
export declare function isWebGpuSupported(): boolean;
|
||||
//# sourceMappingURL=gpu-bruteforce.d.ts.map
|
||||
1
frontend/lib/meshcore-cracker/dist/gpu-bruteforce.d.ts.map
vendored
Normal file
1
frontend/lib/meshcore-cracker/dist/gpu-bruteforce.d.ts.map
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"gpu-bruteforce.d.ts","sourceRoot":"","sources":["../src/gpu-bruteforce.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,mBAAmB;IAClC,KAAK,EAAE,OAAO,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,gBAAgB,CAAC,EAAE,MAAM,EAAE,CAAC;CAC7B;AAED,qBAAa,aAAa;IACxB,OAAO,CAAC,MAAM,CAA0B;IACxC,OAAO,CAAC,QAAQ,CAAmC;IACnD,OAAO,CAAC,eAAe,CAAmC;IAG1D,OAAO,CAAC,YAAY,CAA0B;IAC9C,OAAO,CAAC,gBAAgB,CAA0B;IAClD,OAAO,CAAC,kBAAkB,CAA0B;IACpD,OAAO,CAAC,gBAAgB,CAA0B;IAClD,OAAO,CAAC,oBAAoB,CAAa;IAGzC,OAAO,CAAC,qBAAqB,CAAsD;IACnF,OAAO,CAAC,uBAAuB,CAAsD;IACrF,OAAO,CAAC,sBAAsB,CAAa;IAG3C,OAAO,CAAC,SAAS,CAA6B;IAC9C,OAAO,CAAC,cAAc,CAAiB;IAGvC,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,SAAS,CAAwB;IAGzD,OAAO,CAAC,UAAU,CAkYlB;IAEM,IAAI,IAAI,OAAO,CAAC,OAAO,CAAC;IA8E9B,WAAW,IAAI,OAAO;IAKtB,eAAe,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI;IAK3D,mBAAmB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM;IAIlC,QAAQ,CACZ,iBAAiB,EAAE,MAAM,EACzB,UAAU,EAAE,MAAM,EAClB,WAAW,EAAE,MAAM,EACnB,SAAS,EAAE,MAAM,EACjB,aAAa,CAAC,EAAE,MAAM,EACtB,YAAY,CAAC,EAAE,MAAM,GACpB,OAAO,CAAC,MAAM,EAAE,CAAC;IAkJpB,OAAO,IAAI,IAAI;CA+BhB;AAED;;GAEG;AACH,wBAAgB,iBAAiB,IAAI,OAAO,CAE3C"}
|
||||
645
frontend/lib/meshcore-cracker/dist/gpu-bruteforce.js
vendored
Normal file
645
frontend/lib/meshcore-cracker/dist/gpu-bruteforce.js
vendored
Normal file
@@ -0,0 +1,645 @@
|
||||
// WebGPU-accelerated brute force key cracking for MeshCore packets
|
||||
import { indexToRoomName, countNamesForLength } from './core';
|
||||
export class GpuBruteForce {
|
||||
constructor() {
|
||||
this.device = null;
|
||||
this.pipeline = null;
|
||||
this.bindGroupLayout = null;
|
||||
// Persistent buffers for reuse between batches
|
||||
this.paramsBuffer = null;
|
||||
this.matchCountBuffer = null;
|
||||
this.matchIndicesBuffer = null;
|
||||
this.ciphertextBuffer = null;
|
||||
this.ciphertextBufferSize = 0;
|
||||
// Double-buffered staging buffers for overlapping GPU/CPU work
|
||||
this.matchCountReadBuffers = [null, null];
|
||||
this.matchIndicesReadBuffers = [null, null];
|
||||
this.currentReadBufferIndex = 0;
|
||||
// Cached bind group (recreated only when ciphertext buffer changes)
|
||||
this.bindGroup = null;
|
||||
this.bindGroupDirty = true;
|
||||
// Shader for SHA256 computation
|
||||
this.shaderCode = `
|
||||
// SHA256 round constants
|
||||
const K: array<u32, 64> = array<u32, 64>(
|
||||
0x428a2f98u, 0x71374491u, 0xb5c0fbcfu, 0xe9b5dba5u, 0x3956c25bu, 0x59f111f1u, 0x923f82a4u, 0xab1c5ed5u,
|
||||
0xd807aa98u, 0x12835b01u, 0x243185beu, 0x550c7dc3u, 0x72be5d74u, 0x80deb1feu, 0x9bdc06a7u, 0xc19bf174u,
|
||||
0xe49b69c1u, 0xefbe4786u, 0x0fc19dc6u, 0x240ca1ccu, 0x2de92c6fu, 0x4a7484aau, 0x5cb0a9dcu, 0x76f988dau,
|
||||
0x983e5152u, 0xa831c66du, 0xb00327c8u, 0xbf597fc7u, 0xc6e00bf3u, 0xd5a79147u, 0x06ca6351u, 0x14292967u,
|
||||
0x27b70a85u, 0x2e1b2138u, 0x4d2c6dfcu, 0x53380d13u, 0x650a7354u, 0x766a0abbu, 0x81c2c92eu, 0x92722c85u,
|
||||
0xa2bfe8a1u, 0xa81a664bu, 0xc24b8b70u, 0xc76c51a3u, 0xd192e819u, 0xd6990624u, 0xf40e3585u, 0x106aa070u,
|
||||
0x19a4c116u, 0x1e376c08u, 0x2748774cu, 0x34b0bcb5u, 0x391c0cb3u, 0x4ed8aa4au, 0x5b9cca4fu, 0x682e6ff3u,
|
||||
0x748f82eeu, 0x78a5636fu, 0x84c87814u, 0x8cc70208u, 0x90befffau, 0xa4506cebu, 0xbef9a3f7u, 0xc67178f2u
|
||||
);
|
||||
|
||||
// Character lookup table (a-z = 0-25, 0-9 = 26-35, dash = 36)
|
||||
const CHARS: array<u32, 37> = array<u32, 37>(
|
||||
0x61u, 0x62u, 0x63u, 0x64u, 0x65u, 0x66u, 0x67u, 0x68u, 0x69u, 0x6au, // a-j
|
||||
0x6bu, 0x6cu, 0x6du, 0x6eu, 0x6fu, 0x70u, 0x71u, 0x72u, 0x73u, 0x74u, // k-t
|
||||
0x75u, 0x76u, 0x77u, 0x78u, 0x79u, 0x7au, // u-z
|
||||
0x30u, 0x31u, 0x32u, 0x33u, 0x34u, 0x35u, 0x36u, 0x37u, 0x38u, 0x39u, // 0-9
|
||||
0x2du // dash
|
||||
);
|
||||
|
||||
struct Params {
|
||||
target_channel_hash: u32,
|
||||
batch_offset: u32,
|
||||
name_length: u32,
|
||||
batch_size: u32,
|
||||
target_mac: u32, // First 2 bytes of target MAC (in high 16 bits)
|
||||
ciphertext_words: u32, // Number of 32-bit words in ciphertext
|
||||
ciphertext_len_bits: u32, // Length of ciphertext in bits
|
||||
verify_mac: u32, // 1 to verify MAC, 0 to skip
|
||||
}
|
||||
|
||||
@group(0) @binding(0) var<uniform> params: Params;
|
||||
@group(0) @binding(1) var<storage, read_write> match_count: atomic<u32>;
|
||||
@group(0) @binding(2) var<storage, read_write> match_indices: array<u32>;
|
||||
@group(0) @binding(3) var<storage, read> ciphertext: array<u32>; // Ciphertext data
|
||||
|
||||
fn rotr(x: u32, n: u32) -> u32 {
|
||||
return (x >> n) | (x << (32u - n));
|
||||
}
|
||||
|
||||
fn ch(x: u32, y: u32, z: u32) -> u32 {
|
||||
return (x & y) ^ (~x & z);
|
||||
}
|
||||
|
||||
fn maj(x: u32, y: u32, z: u32) -> u32 {
|
||||
return (x & y) ^ (x & z) ^ (y & z);
|
||||
}
|
||||
|
||||
fn sigma0(x: u32) -> u32 {
|
||||
return rotr(x, 2u) ^ rotr(x, 13u) ^ rotr(x, 22u);
|
||||
}
|
||||
|
||||
fn sigma1(x: u32) -> u32 {
|
||||
return rotr(x, 6u) ^ rotr(x, 11u) ^ rotr(x, 25u);
|
||||
}
|
||||
|
||||
fn gamma0(x: u32) -> u32 {
|
||||
return rotr(x, 7u) ^ rotr(x, 18u) ^ (x >> 3u);
|
||||
}
|
||||
|
||||
fn gamma1(x: u32) -> u32 {
|
||||
return rotr(x, 17u) ^ rotr(x, 19u) ^ (x >> 10u);
|
||||
}
|
||||
|
||||
// Convert index to room name bytes, returns the hash as a u32 for the first byte check
|
||||
fn index_to_room_name(idx: u32, length: u32, msg: ptr<function, array<u32, 16>>) -> bool {
|
||||
// Message starts with '#' (0x23)
|
||||
var byte_pos = 0u;
|
||||
var word_idx = 0u;
|
||||
var current_word = 0x23000000u; // '#' in big-endian position 0
|
||||
byte_pos = 1u;
|
||||
|
||||
var remaining = idx;
|
||||
var prev_was_dash = false;
|
||||
|
||||
// Generate room name from index
|
||||
for (var i = 0u; i < length; i++) {
|
||||
let char_count = select(37u, 36u, i == 0u || i == length - 1u); // no dash at start/end
|
||||
var char_idx = remaining % char_count;
|
||||
remaining = remaining / char_count;
|
||||
|
||||
// Check for consecutive dashes (invalid)
|
||||
let is_dash = char_idx == 36u && i > 0u && i < length - 1u;
|
||||
if (is_dash && prev_was_dash) {
|
||||
return false; // Invalid: consecutive dashes
|
||||
}
|
||||
prev_was_dash = is_dash;
|
||||
|
||||
// Map char index to actual character
|
||||
let c = CHARS[char_idx];
|
||||
|
||||
// Pack byte into current word (big-endian)
|
||||
let shift = (3u - byte_pos % 4u) * 8u;
|
||||
if (byte_pos % 4u == 0u && byte_pos > 0u) {
|
||||
(*msg)[word_idx] = current_word;
|
||||
word_idx = word_idx + 1u;
|
||||
current_word = 0u;
|
||||
}
|
||||
current_word = current_word | (c << shift);
|
||||
byte_pos = byte_pos + 1u;
|
||||
}
|
||||
|
||||
// Add padding: 0x80 followed by zeros, then length in bits
|
||||
let msg_len_bits = (length + 1u) * 8u; // +1 for '#'
|
||||
|
||||
// Add 0x80 padding byte
|
||||
let shift = (3u - byte_pos % 4u) * 8u;
|
||||
if (byte_pos % 4u == 0u) {
|
||||
(*msg)[word_idx] = current_word;
|
||||
word_idx = word_idx + 1u;
|
||||
current_word = 0x80000000u;
|
||||
} else {
|
||||
current_word = current_word | (0x80u << shift);
|
||||
}
|
||||
byte_pos = byte_pos + 1u;
|
||||
|
||||
// Store current word
|
||||
if (byte_pos % 4u == 0u || word_idx < 14u) {
|
||||
(*msg)[word_idx] = current_word;
|
||||
word_idx = word_idx + 1u;
|
||||
}
|
||||
|
||||
// Zero-fill until word 14
|
||||
for (var i = word_idx; i < 14u; i++) {
|
||||
(*msg)[i] = 0u;
|
||||
}
|
||||
|
||||
// Length in bits (64-bit, but we only use lower 32 bits for short messages)
|
||||
(*msg)[14u] = 0u;
|
||||
(*msg)[15u] = msg_len_bits;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
fn sha256_block(msg: ptr<function, array<u32, 16>>) -> array<u32, 8> {
|
||||
// Initialize hash values
|
||||
var h: array<u32, 8> = array<u32, 8>(
|
||||
0x6a09e667u, 0xbb67ae85u, 0x3c6ef372u, 0xa54ff53au,
|
||||
0x510e527fu, 0x9b05688cu, 0x1f83d9abu, 0x5be0cd19u
|
||||
);
|
||||
|
||||
// Message schedule
|
||||
var w: array<u32, 64>;
|
||||
for (var i = 0u; i < 16u; i++) {
|
||||
w[i] = (*msg)[i];
|
||||
}
|
||||
for (var i = 16u; i < 64u; i++) {
|
||||
w[i] = gamma1(w[i-2u]) + w[i-7u] + gamma0(w[i-15u]) + w[i-16u];
|
||||
}
|
||||
|
||||
// Compression
|
||||
var a = h[0]; var b = h[1]; var c = h[2]; var d = h[3];
|
||||
var e = h[4]; var f = h[5]; var g = h[6]; var hh = h[7];
|
||||
|
||||
for (var i = 0u; i < 64u; i++) {
|
||||
let t1 = hh + sigma1(e) + ch(e, f, g) + K[i] + w[i];
|
||||
let t2 = sigma0(a) + maj(a, b, c);
|
||||
hh = g; g = f; f = e; e = d + t1;
|
||||
d = c; c = b; b = a; a = t1 + t2;
|
||||
}
|
||||
|
||||
h[0] = h[0] + a; h[1] = h[1] + b; h[2] = h[2] + c; h[3] = h[3] + d;
|
||||
h[4] = h[4] + e; h[5] = h[5] + f; h[6] = h[6] + g; h[7] = h[7] + hh;
|
||||
|
||||
return h;
|
||||
}
|
||||
|
||||
// Compute SHA256 of the key (16 bytes) to get channel hash
|
||||
fn sha256_key(key: array<u32, 4>) -> u32 {
|
||||
var msg: array<u32, 16>;
|
||||
|
||||
// Key bytes (16 bytes = 4 words)
|
||||
msg[0] = key[0];
|
||||
msg[1] = key[1];
|
||||
msg[2] = key[2];
|
||||
msg[3] = key[3];
|
||||
|
||||
// Padding: 0x80 followed by zeros
|
||||
msg[4] = 0x80000000u;
|
||||
for (var i = 5u; i < 14u; i++) {
|
||||
msg[i] = 0u;
|
||||
}
|
||||
|
||||
// Length: 128 bits
|
||||
msg[14] = 0u;
|
||||
msg[15] = 128u;
|
||||
|
||||
let hash = sha256_block(&msg);
|
||||
|
||||
// Return first byte of hash (big-endian)
|
||||
return hash[0] >> 24u;
|
||||
}
|
||||
|
||||
// HMAC-SHA256 for MAC verification
|
||||
// Key is 16 bytes (4 words), padded to 32 bytes with zeros for MeshCore
|
||||
// Returns first 2 bytes of HMAC (as u32 in high 16 bits)
|
||||
fn hmac_sha256_mac(key: array<u32, 4>, ciphertext_len: u32) -> u32 {
|
||||
// HMAC: H((K' ^ opad) || H((K' ^ ipad) || message))
|
||||
// K' is 64 bytes (32 bytes key + 32 bytes zero padding for MeshCore, then padded to 64)
|
||||
// ipad = 0x36 repeated, opad = 0x5c repeated
|
||||
|
||||
// Build padded key (64 bytes = 16 words)
|
||||
// MeshCore uses 32-byte secret: 16-byte key + 16 zero bytes
|
||||
var k_pad: array<u32, 16>;
|
||||
k_pad[0] = key[0];
|
||||
k_pad[1] = key[1];
|
||||
k_pad[2] = key[2];
|
||||
k_pad[3] = key[3];
|
||||
for (var i = 4u; i < 16u; i++) {
|
||||
k_pad[i] = 0u;
|
||||
}
|
||||
|
||||
// Inner hash: SHA256((K' ^ ipad) || message)
|
||||
// First block: K' ^ ipad (64 bytes)
|
||||
var inner_block: array<u32, 16>;
|
||||
for (var i = 0u; i < 16u; i++) {
|
||||
inner_block[i] = k_pad[i] ^ 0x36363636u;
|
||||
}
|
||||
|
||||
// Initialize hash state with first block
|
||||
var h: array<u32, 8> = sha256_block(&inner_block);
|
||||
|
||||
// Process ciphertext blocks (continuing from h state)
|
||||
let ciphertext_words = params.ciphertext_words;
|
||||
var word_idx = 0u;
|
||||
|
||||
// Process full 64-byte blocks of ciphertext
|
||||
while (word_idx + 16u <= ciphertext_words) {
|
||||
var block: array<u32, 16>;
|
||||
for (var i = 0u; i < 16u; i++) {
|
||||
block[i] = ciphertext[word_idx + i];
|
||||
}
|
||||
h = sha256_block_continue(&block, h);
|
||||
word_idx = word_idx + 16u;
|
||||
}
|
||||
|
||||
// Final block with remaining ciphertext + padding
|
||||
var final_block: array<u32, 16>;
|
||||
var remaining = ciphertext_words - word_idx;
|
||||
for (var i = 0u; i < 16u; i++) {
|
||||
if (i < remaining) {
|
||||
final_block[i] = ciphertext[word_idx + i];
|
||||
} else if (i == remaining) {
|
||||
// Add 0x80 padding
|
||||
final_block[i] = 0x80000000u;
|
||||
} else {
|
||||
final_block[i] = 0u;
|
||||
}
|
||||
}
|
||||
|
||||
// Add length (64 bytes of ipad + ciphertext length)
|
||||
let total_bits = 512u + params.ciphertext_len_bits;
|
||||
if (remaining < 14u) {
|
||||
final_block[14] = 0u;
|
||||
final_block[15] = total_bits;
|
||||
h = sha256_block_continue(&final_block, h);
|
||||
} else {
|
||||
// Need extra block for length
|
||||
h = sha256_block_continue(&final_block, h);
|
||||
var len_block: array<u32, 16>;
|
||||
for (var i = 0u; i < 14u; i++) {
|
||||
len_block[i] = 0u;
|
||||
}
|
||||
len_block[14] = 0u;
|
||||
len_block[15] = total_bits;
|
||||
h = sha256_block_continue(&len_block, h);
|
||||
}
|
||||
|
||||
let inner_hash = h;
|
||||
|
||||
// Outer hash: SHA256((K' ^ opad) || inner_hash)
|
||||
var outer_block: array<u32, 16>;
|
||||
for (var i = 0u; i < 16u; i++) {
|
||||
outer_block[i] = k_pad[i] ^ 0x5c5c5c5cu;
|
||||
}
|
||||
h = sha256_block(&outer_block);
|
||||
|
||||
// Second block: inner_hash (32 bytes) + padding
|
||||
var hash_block: array<u32, 16>;
|
||||
for (var i = 0u; i < 8u; i++) {
|
||||
hash_block[i] = inner_hash[i];
|
||||
}
|
||||
hash_block[8] = 0x80000000u;
|
||||
for (var i = 9u; i < 14u; i++) {
|
||||
hash_block[i] = 0u;
|
||||
}
|
||||
hash_block[14] = 0u;
|
||||
hash_block[15] = 512u + 256u; // 64 bytes opad + 32 bytes inner hash
|
||||
|
||||
h = sha256_block_continue(&hash_block, h);
|
||||
|
||||
// Return first 2 bytes (high 16 bits of first word)
|
||||
return h[0] & 0xFFFF0000u;
|
||||
}
|
||||
|
||||
// SHA256 block computation continuing from existing state
|
||||
fn sha256_block_continue(msg: ptr<function, array<u32, 16>>, h_in: array<u32, 8>) -> array<u32, 8> {
|
||||
var h = h_in;
|
||||
|
||||
// Message schedule
|
||||
var w: array<u32, 64>;
|
||||
for (var i = 0u; i < 16u; i++) {
|
||||
w[i] = (*msg)[i];
|
||||
}
|
||||
for (var i = 16u; i < 64u; i++) {
|
||||
w[i] = gamma1(w[i-2u]) + w[i-7u] + gamma0(w[i-15u]) + w[i-16u];
|
||||
}
|
||||
|
||||
// Compression
|
||||
var a = h[0]; var b = h[1]; var c = h[2]; var d = h[3];
|
||||
var e = h[4]; var f = h[5]; var g = h[6]; var hh = h[7];
|
||||
|
||||
for (var i = 0u; i < 64u; i++) {
|
||||
let t1 = hh + sigma1(e) + ch(e, f, g) + K[i] + w[i];
|
||||
let t2 = sigma0(a) + maj(a, b, c);
|
||||
hh = g; g = f; f = e; e = d + t1;
|
||||
d = c; c = b; b = a; a = t1 + t2;
|
||||
}
|
||||
|
||||
h[0] = h[0] + a; h[1] = h[1] + b; h[2] = h[2] + c; h[3] = h[3] + d;
|
||||
h[4] = h[4] + e; h[5] = h[5] + f; h[6] = h[6] + g; h[7] = h[7] + hh;
|
||||
|
||||
return h;
|
||||
}
|
||||
|
||||
// Process a single candidate and record match if found
|
||||
fn process_candidate(name_idx: u32) {
|
||||
// Generate message for this room name
|
||||
var msg: array<u32, 16>;
|
||||
let valid = index_to_room_name(name_idx, params.name_length, &msg);
|
||||
|
||||
if (!valid) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Compute SHA256("#roomname") - this gives us the key
|
||||
let key_hash = sha256_block(&msg);
|
||||
|
||||
// Take first 16 bytes (4 words) as the key
|
||||
var key: array<u32, 4>;
|
||||
key[0] = key_hash[0];
|
||||
key[1] = key_hash[1];
|
||||
key[2] = key_hash[2];
|
||||
key[3] = key_hash[3];
|
||||
|
||||
// Compute SHA256(key) to get channel hash
|
||||
let channel_hash = sha256_key(key);
|
||||
|
||||
// Check if channel hash matches target
|
||||
if (channel_hash != params.target_channel_hash) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Channel hash matches - verify MAC if enabled
|
||||
if (params.verify_mac == 1u) {
|
||||
let computed_mac = hmac_sha256_mac(key, params.ciphertext_len_bits);
|
||||
if (computed_mac != params.target_mac) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Found a match - record the index
|
||||
let match_idx = atomicAdd(&match_count, 1u);
|
||||
if (match_idx < 1024u) { // Limit stored matches
|
||||
match_indices[match_idx] = name_idx;
|
||||
}
|
||||
}
|
||||
|
||||
// Each thread processes 16 candidates to amortize thread overhead
|
||||
const CANDIDATES_PER_THREAD: u32 = 16u;
|
||||
|
||||
@compute @workgroup_size(256)
|
||||
fn main(@builtin(global_invocation_id) global_id: vec3<u32>) {
|
||||
let base_idx = global_id.x * CANDIDATES_PER_THREAD;
|
||||
|
||||
for (var i = 0u; i < CANDIDATES_PER_THREAD; i++) {
|
||||
let idx = base_idx + i;
|
||||
if (idx >= params.batch_size) {
|
||||
return;
|
||||
}
|
||||
let name_idx = params.batch_offset + idx;
|
||||
process_candidate(name_idx);
|
||||
}
|
||||
}
|
||||
`;
|
||||
}
|
||||
async init() {
|
||||
if (!navigator.gpu) {
|
||||
console.warn('WebGPU not supported');
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
const adapter = await navigator.gpu.requestAdapter();
|
||||
if (!adapter) {
|
||||
console.warn('No GPU adapter found');
|
||||
return false;
|
||||
}
|
||||
this.device = await adapter.requestDevice();
|
||||
// Create bind group layout
|
||||
this.bindGroupLayout = this.device.createBindGroupLayout({
|
||||
entries: [
|
||||
{ binding: 0, visibility: GPUShaderStage.COMPUTE, buffer: { type: 'uniform' } },
|
||||
{ binding: 1, visibility: GPUShaderStage.COMPUTE, buffer: { type: 'storage' } },
|
||||
{ binding: 2, visibility: GPUShaderStage.COMPUTE, buffer: { type: 'storage' } },
|
||||
{ binding: 3, visibility: GPUShaderStage.COMPUTE, buffer: { type: 'read-only-storage' } },
|
||||
],
|
||||
});
|
||||
// Create persistent buffers
|
||||
this.paramsBuffer = this.device.createBuffer({
|
||||
size: 32, // 8 u32s
|
||||
usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
|
||||
});
|
||||
this.matchCountBuffer = this.device.createBuffer({
|
||||
size: 4,
|
||||
usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST,
|
||||
});
|
||||
this.matchIndicesBuffer = this.device.createBuffer({
|
||||
size: 1024 * 4, // Max 1024 matches per batch
|
||||
usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC,
|
||||
});
|
||||
// Double-buffered staging buffers
|
||||
for (let i = 0; i < 2; i++) {
|
||||
this.matchCountReadBuffers[i] = this.device.createBuffer({
|
||||
size: 4,
|
||||
usage: GPUBufferUsage.MAP_READ | GPUBufferUsage.COPY_DST,
|
||||
});
|
||||
this.matchIndicesReadBuffers[i] = this.device.createBuffer({
|
||||
size: 1024 * 4,
|
||||
usage: GPUBufferUsage.MAP_READ | GPUBufferUsage.COPY_DST,
|
||||
});
|
||||
}
|
||||
// Create pipeline
|
||||
const shaderModule = this.device.createShaderModule({
|
||||
code: this.shaderCode,
|
||||
});
|
||||
const pipelineLayout = this.device.createPipelineLayout({
|
||||
bindGroupLayouts: [this.bindGroupLayout],
|
||||
});
|
||||
this.pipeline = this.device.createComputePipeline({
|
||||
layout: pipelineLayout,
|
||||
compute: {
|
||||
module: shaderModule,
|
||||
entryPoint: 'main',
|
||||
},
|
||||
});
|
||||
return true;
|
||||
}
|
||||
catch (e) {
|
||||
console.error('WebGPU initialization failed:', e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
isAvailable() {
|
||||
return this.device !== null && this.pipeline !== null;
|
||||
}
|
||||
// Convert room name index to actual room name string (delegates to core)
|
||||
indexToRoomName(idx, length) {
|
||||
return indexToRoomName(length, idx);
|
||||
}
|
||||
// Count valid names for a given length (delegates to core)
|
||||
countNamesForLength(len) {
|
||||
return countNamesForLength(len);
|
||||
}
|
||||
async runBatch(targetChannelHash, nameLength, batchOffset, batchSize, ciphertextHex, targetMacHex) {
|
||||
if (!this.device ||
|
||||
!this.pipeline ||
|
||||
!this.bindGroupLayout ||
|
||||
!this.paramsBuffer ||
|
||||
!this.matchCountBuffer ||
|
||||
!this.matchIndicesBuffer ||
|
||||
!this.matchCountReadBuffers[0] ||
|
||||
!this.matchCountReadBuffers[1] ||
|
||||
!this.matchIndicesReadBuffers[0] ||
|
||||
!this.matchIndicesReadBuffers[1]) {
|
||||
throw new Error('GPU not initialized');
|
||||
}
|
||||
// Swap to alternate staging buffer set (double-buffering)
|
||||
const readBufferIdx = this.currentReadBufferIndex;
|
||||
this.currentReadBufferIndex = 1 - this.currentReadBufferIndex;
|
||||
const matchCountReadBuffer = this.matchCountReadBuffers[readBufferIdx];
|
||||
const matchIndicesReadBuffer = this.matchIndicesReadBuffers[readBufferIdx];
|
||||
// Parse ciphertext if provided
|
||||
const verifyMac = ciphertextHex && targetMacHex ? 1 : 0;
|
||||
let ciphertextWords;
|
||||
let ciphertextLenBits = 0;
|
||||
let targetMac = 0;
|
||||
if (verifyMac) {
|
||||
// Convert hex to bytes then to big-endian u32 words
|
||||
const ciphertextBytes = new Uint8Array(ciphertextHex.length / 2);
|
||||
for (let i = 0; i < ciphertextBytes.length; i++) {
|
||||
ciphertextBytes[i] = parseInt(ciphertextHex.substr(i * 2, 2), 16);
|
||||
}
|
||||
ciphertextLenBits = ciphertextBytes.length * 8;
|
||||
// Pad to 4-byte boundary and convert to big-endian u32
|
||||
const paddedLen = Math.ceil(ciphertextBytes.length / 4) * 4;
|
||||
const padded = new Uint8Array(paddedLen);
|
||||
padded.set(ciphertextBytes);
|
||||
ciphertextWords = new Uint32Array(paddedLen / 4);
|
||||
for (let i = 0; i < ciphertextWords.length; i++) {
|
||||
ciphertextWords[i] =
|
||||
(padded[i * 4] << 24) |
|
||||
(padded[i * 4 + 1] << 16) |
|
||||
(padded[i * 4 + 2] << 8) |
|
||||
padded[i * 4 + 3];
|
||||
}
|
||||
// Parse target MAC (2 bytes in high 16 bits)
|
||||
const macByte0 = parseInt(targetMacHex.substr(0, 2), 16);
|
||||
const macByte1 = parseInt(targetMacHex.substr(2, 2), 16);
|
||||
targetMac = (macByte0 << 24) | (macByte1 << 16);
|
||||
}
|
||||
else {
|
||||
ciphertextWords = new Uint32Array([0]); // Dummy
|
||||
}
|
||||
// Resize ciphertext buffer if needed (marks bind group as dirty)
|
||||
const requiredCiphertextSize = Math.max(ciphertextWords.length * 4, 4);
|
||||
if (!this.ciphertextBuffer || this.ciphertextBufferSize < requiredCiphertextSize) {
|
||||
if (this.ciphertextBuffer) {
|
||||
this.ciphertextBuffer.destroy();
|
||||
}
|
||||
this.ciphertextBuffer = this.device.createBuffer({
|
||||
size: requiredCiphertextSize,
|
||||
usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST,
|
||||
});
|
||||
this.ciphertextBufferSize = requiredCiphertextSize;
|
||||
this.bindGroupDirty = true;
|
||||
}
|
||||
// Write params
|
||||
const paramsData = new Uint32Array([
|
||||
targetChannelHash,
|
||||
batchOffset,
|
||||
nameLength,
|
||||
batchSize,
|
||||
targetMac,
|
||||
ciphertextWords.length,
|
||||
ciphertextLenBits,
|
||||
verifyMac,
|
||||
]);
|
||||
this.device.queue.writeBuffer(this.paramsBuffer, 0, paramsData);
|
||||
// Write ciphertext
|
||||
this.device.queue.writeBuffer(this.ciphertextBuffer, 0, ciphertextWords);
|
||||
// Reset match count (reuse static zero buffer)
|
||||
this.device.queue.writeBuffer(this.matchCountBuffer, 0, GpuBruteForce.ZERO_DATA);
|
||||
// Recreate bind group only if needed
|
||||
if (this.bindGroupDirty || !this.bindGroup) {
|
||||
this.bindGroup = this.device.createBindGroup({
|
||||
layout: this.bindGroupLayout,
|
||||
entries: [
|
||||
{ binding: 0, resource: { buffer: this.paramsBuffer } },
|
||||
{ binding: 1, resource: { buffer: this.matchCountBuffer } },
|
||||
{ binding: 2, resource: { buffer: this.matchIndicesBuffer } },
|
||||
{ binding: 3, resource: { buffer: this.ciphertextBuffer } },
|
||||
],
|
||||
});
|
||||
this.bindGroupDirty = false;
|
||||
}
|
||||
// Create command encoder
|
||||
const commandEncoder = this.device.createCommandEncoder();
|
||||
const passEncoder = commandEncoder.beginComputePass();
|
||||
passEncoder.setPipeline(this.pipeline);
|
||||
passEncoder.setBindGroup(0, this.bindGroup);
|
||||
// Each workgroup has 256 threads, each processing 16 candidates
|
||||
const CANDIDATES_PER_THREAD = 16;
|
||||
passEncoder.dispatchWorkgroups(Math.ceil(batchSize / (256 * CANDIDATES_PER_THREAD)));
|
||||
passEncoder.end();
|
||||
// Copy results to current staging buffers
|
||||
commandEncoder.copyBufferToBuffer(this.matchCountBuffer, 0, matchCountReadBuffer, 0, 4);
|
||||
commandEncoder.copyBufferToBuffer(this.matchIndicesBuffer, 0, matchIndicesReadBuffer, 0, 1024 * 4);
|
||||
// Submit
|
||||
this.device.queue.submit([commandEncoder.finish()]);
|
||||
// Read results from current staging buffers
|
||||
await matchCountReadBuffer.mapAsync(GPUMapMode.READ);
|
||||
const matchCount = new Uint32Array(matchCountReadBuffer.getMappedRange())[0];
|
||||
matchCountReadBuffer.unmap();
|
||||
const matches = [];
|
||||
if (matchCount > 0) {
|
||||
await matchIndicesReadBuffer.mapAsync(GPUMapMode.READ);
|
||||
const indices = new Uint32Array(matchIndicesReadBuffer.getMappedRange());
|
||||
for (let i = 0; i < Math.min(matchCount, 1024); i++) {
|
||||
matches.push(indices[i]);
|
||||
}
|
||||
matchIndicesReadBuffer.unmap();
|
||||
}
|
||||
return matches;
|
||||
}
|
||||
destroy() {
|
||||
// Clean up persistent buffers
|
||||
this.paramsBuffer?.destroy();
|
||||
this.matchCountBuffer?.destroy();
|
||||
this.matchIndicesBuffer?.destroy();
|
||||
this.ciphertextBuffer?.destroy();
|
||||
// Clean up double-buffered staging buffers
|
||||
this.matchCountReadBuffers[0]?.destroy();
|
||||
this.matchCountReadBuffers[1]?.destroy();
|
||||
this.matchIndicesReadBuffers[0]?.destroy();
|
||||
this.matchIndicesReadBuffers[1]?.destroy();
|
||||
this.paramsBuffer = null;
|
||||
this.matchCountBuffer = null;
|
||||
this.matchIndicesBuffer = null;
|
||||
this.ciphertextBuffer = null;
|
||||
this.ciphertextBufferSize = 0;
|
||||
this.matchCountReadBuffers = [null, null];
|
||||
this.matchIndicesReadBuffers = [null, null];
|
||||
this.currentReadBufferIndex = 0;
|
||||
this.bindGroup = null;
|
||||
this.bindGroupDirty = true;
|
||||
if (this.device) {
|
||||
this.device.destroy();
|
||||
this.device = null;
|
||||
}
|
||||
this.pipeline = null;
|
||||
this.bindGroupLayout = null;
|
||||
}
|
||||
}
|
||||
// Reusable zero buffer for resetting match count
|
||||
GpuBruteForce.ZERO_DATA = new Uint32Array([0]);
|
||||
/**
|
||||
* Check if WebGPU is supported in the current browser.
|
||||
*/
|
||||
export function isWebGpuSupported() {
|
||||
return typeof navigator !== 'undefined' && 'gpu' in navigator;
|
||||
}
|
||||
//# sourceMappingURL=gpu-bruteforce.js.map
|
||||
1
frontend/lib/meshcore-cracker/dist/gpu-bruteforce.js.map
vendored
Normal file
1
frontend/lib/meshcore-cracker/dist/gpu-bruteforce.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
33
frontend/lib/meshcore-cracker/dist/index.d.ts
vendored
Normal file
33
frontend/lib/meshcore-cracker/dist/index.d.ts
vendored
Normal file
@@ -0,0 +1,33 @@
|
||||
/**
|
||||
* MeshCore Cracker - Standalone library for cracking MeshCore GroupText packets
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* import { GroupTextCracker } from 'meshcore-cracker';
|
||||
*
|
||||
* const cracker = new GroupTextCracker();
|
||||
*
|
||||
* // Optional: load wordlist for dictionary attack
|
||||
* await cracker.loadWordlist('/words_alpha.txt');
|
||||
*
|
||||
* const result = await cracker.crack(packetHex, {
|
||||
* maxLength: 6,
|
||||
* useTimestampFilter: true,
|
||||
* useUtf8Filter: true,
|
||||
* }, (progress) => {
|
||||
* console.log(`${progress.percent.toFixed(1)}% - ETA: ${progress.etaSeconds}s`);
|
||||
* });
|
||||
*
|
||||
* if (result.found) {
|
||||
* console.log(`Room: #${result.roomName}`);
|
||||
* console.log(`Message: ${result.decryptedMessage}`);
|
||||
* }
|
||||
*
|
||||
* cracker.destroy();
|
||||
* ```
|
||||
*/
|
||||
export { GroupTextCracker } from './cracker';
|
||||
export type { CrackOptions, CrackResult, ProgressReport, ProgressCallback, DecodedPacket, } from './types';
|
||||
export { deriveKeyFromRoomName, getChannelHash, verifyMac, isTimestampValid, isValidUtf8, indexToRoomName, roomNameToIndex, countNamesForLength, PUBLIC_ROOM_NAME, PUBLIC_KEY, } from './core';
|
||||
export { isWebGpuSupported } from './gpu-bruteforce';
|
||||
//# sourceMappingURL=index.d.ts.map
|
||||
1
frontend/lib/meshcore-cracker/dist/index.d.ts.map
vendored
Normal file
1
frontend/lib/meshcore-cracker/dist/index.d.ts.map
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAGH,OAAO,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAG7C,YAAY,EACV,YAAY,EACZ,WAAW,EACX,cAAc,EACd,gBAAgB,EAChB,aAAa,GACd,MAAM,SAAS,CAAC;AAGjB,OAAO,EACL,qBAAqB,EACrB,cAAc,EACd,SAAS,EACT,gBAAgB,EAChB,WAAW,EACX,eAAe,EACf,eAAe,EACf,mBAAmB,EACnB,gBAAgB,EAChB,UAAU,GACX,MAAM,QAAQ,CAAC;AAEhB,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC"}
|
||||
11476
frontend/lib/meshcore-cracker/dist/index.js
vendored
Normal file
11476
frontend/lib/meshcore-cracker/dist/index.js
vendored
Normal file
File diff suppressed because it is too large
Load Diff
7
frontend/lib/meshcore-cracker/dist/index.js.map
vendored
Normal file
7
frontend/lib/meshcore-cracker/dist/index.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
87
frontend/lib/meshcore-cracker/dist/types.d.ts
vendored
Normal file
87
frontend/lib/meshcore-cracker/dist/types.d.ts
vendored
Normal file
@@ -0,0 +1,87 @@
|
||||
/**
|
||||
* Options for configuring the cracking process.
|
||||
*/
|
||||
export interface CrackOptions {
|
||||
/**
|
||||
* Maximum room name length to search (default: 8).
|
||||
* Longer names exponentially increase search time.
|
||||
*/
|
||||
maxLength?: number;
|
||||
/**
|
||||
* Filter results by timestamp validity (default: true).
|
||||
* When enabled, rejects results where the decrypted timestamp
|
||||
* is more than 30 days old.
|
||||
*/
|
||||
useTimestampFilter?: boolean;
|
||||
/**
|
||||
* Filter results by UTF-8 validity (default: true).
|
||||
* When enabled, rejects results containing invalid UTF-8 sequences.
|
||||
*/
|
||||
useUtf8Filter?: boolean;
|
||||
/**
|
||||
* Resume cracking from a specific room name position.
|
||||
* Useful for resuming interrupted searches.
|
||||
*/
|
||||
startFrom?: string;
|
||||
}
|
||||
/**
|
||||
* Progress information reported during cracking.
|
||||
*/
|
||||
export interface ProgressReport {
|
||||
/** Total candidates checked so far */
|
||||
checked: number;
|
||||
/** Total candidates to check */
|
||||
total: number;
|
||||
/** Progress percentage (0-100) */
|
||||
percent: number;
|
||||
/** Current cracking rate in keys/second */
|
||||
rateKeysPerSec: number;
|
||||
/** Estimated time remaining in seconds */
|
||||
etaSeconds: number;
|
||||
/** Time elapsed since start in seconds */
|
||||
elapsedSeconds: number;
|
||||
/** Current room name length being tested */
|
||||
currentLength: number;
|
||||
/** Current room name position being tested */
|
||||
currentPosition: string;
|
||||
/** Current phase of cracking */
|
||||
phase: 'public-key' | 'wordlist' | 'bruteforce';
|
||||
}
|
||||
/**
|
||||
* Callback function for progress updates.
|
||||
* Called approximately 5 times per second during cracking.
|
||||
*/
|
||||
export type ProgressCallback = (report: ProgressReport) => void;
|
||||
/**
|
||||
* Result of a cracking operation.
|
||||
*/
|
||||
export interface CrackResult {
|
||||
/** Whether a matching room name was found */
|
||||
found: boolean;
|
||||
/** The room name (without '#' prefix) if found */
|
||||
roomName?: string;
|
||||
/** The derived encryption key (hex) if found */
|
||||
key?: string;
|
||||
/** The decrypted message content if found */
|
||||
decryptedMessage?: string;
|
||||
/** Whether the operation was aborted */
|
||||
aborted?: boolean;
|
||||
/** Position to resume from if aborted or failed */
|
||||
resumeFrom?: string;
|
||||
/** Error message if an error occurred */
|
||||
error?: string;
|
||||
}
|
||||
/**
|
||||
* Decoded packet information extracted from a MeshCore GroupText packet.
|
||||
*/
|
||||
export interface DecodedPacket {
|
||||
/** Channel hash (1 byte, hex) */
|
||||
channelHash: string;
|
||||
/** Encrypted ciphertext (hex) */
|
||||
ciphertext: string;
|
||||
/** MAC for verification (2 bytes, hex) */
|
||||
cipherMac: string;
|
||||
/** Whether this is a GroupText packet */
|
||||
isGroupText: boolean;
|
||||
}
|
||||
//# sourceMappingURL=types.d.ts.map
|
||||
1
frontend/lib/meshcore-cracker/dist/types.d.ts.map
vendored
Normal file
1
frontend/lib/meshcore-cracker/dist/types.d.ts.map
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB;;;;OAIG;IACH,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAE7B;;;OAGG;IACH,aAAa,CAAC,EAAE,OAAO,CAAC;IAExB;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,sCAAsC;IACtC,OAAO,EAAE,MAAM,CAAC;IAEhB,gCAAgC;IAChC,KAAK,EAAE,MAAM,CAAC;IAEd,kCAAkC;IAClC,OAAO,EAAE,MAAM,CAAC;IAEhB,2CAA2C;IAC3C,cAAc,EAAE,MAAM,CAAC;IAEvB,0CAA0C;IAC1C,UAAU,EAAE,MAAM,CAAC;IAEnB,0CAA0C;IAC1C,cAAc,EAAE,MAAM,CAAC;IAEvB,4CAA4C;IAC5C,aAAa,EAAE,MAAM,CAAC;IAEtB,8CAA8C;IAC9C,eAAe,EAAE,MAAM,CAAC;IAExB,gCAAgC;IAChC,KAAK,EAAE,YAAY,GAAG,UAAU,GAAG,YAAY,CAAC;CACjD;AAED;;;GAGG;AACH,MAAM,MAAM,gBAAgB,GAAG,CAAC,MAAM,EAAE,cAAc,KAAK,IAAI,CAAC;AAEhE;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,6CAA6C;IAC7C,KAAK,EAAE,OAAO,CAAC;IAEf,kDAAkD;IAClD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAElB,gDAAgD;IAChD,GAAG,CAAC,EAAE,MAAM,CAAC;IAEb,6CAA6C;IAC7C,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAE1B,wCAAwC;IACxC,OAAO,CAAC,EAAE,OAAO,CAAC;IAElB,mDAAmD;IACnD,UAAU,CAAC,EAAE,MAAM,CAAC;IAEpB,yCAAyC;IACzC,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,iCAAiC;IACjC,WAAW,EAAE,MAAM,CAAC;IAEpB,iCAAiC;IACjC,UAAU,EAAE,MAAM,CAAC;IAEnB,0CAA0C;IAC1C,SAAS,EAAE,MAAM,CAAC;IAElB,yCAAyC;IACzC,WAAW,EAAE,OAAO,CAAC;CACtB"}
|
||||
2
frontend/lib/meshcore-cracker/dist/types.js
vendored
Normal file
2
frontend/lib/meshcore-cracker/dist/types.js
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
export {};
|
||||
//# sourceMappingURL=types.js.map
|
||||
1
frontend/lib/meshcore-cracker/dist/types.js.map
vendored
Normal file
1
frontend/lib/meshcore-cracker/dist/types.js.map
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
|
||||
1
frontend/lib/meshcore-cracker/node_modules/.bin/esbuild
generated
vendored
Symbolic link
1
frontend/lib/meshcore-cracker/node_modules/.bin/esbuild
generated
vendored
Symbolic link
@@ -0,0 +1 @@
|
||||
../esbuild/bin/esbuild
|
||||
3
frontend/lib/meshcore-cracker/node_modules/@esbuild/linux-x64/README.md
generated
vendored
Normal file
3
frontend/lib/meshcore-cracker/node_modules/@esbuild/linux-x64/README.md
generated
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
# esbuild
|
||||
|
||||
This is the Linux 64-bit binary for esbuild, a JavaScript bundler and minifier. See https://github.com/evanw/esbuild for details.
|
||||
BIN
frontend/lib/meshcore-cracker/node_modules/@esbuild/linux-x64/bin/esbuild
generated
vendored
Normal file
BIN
frontend/lib/meshcore-cracker/node_modules/@esbuild/linux-x64/bin/esbuild
generated
vendored
Normal file
Binary file not shown.
20
frontend/lib/meshcore-cracker/node_modules/@esbuild/linux-x64/package.json
generated
vendored
Normal file
20
frontend/lib/meshcore-cracker/node_modules/@esbuild/linux-x64/package.json
generated
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"name": "@esbuild/linux-x64",
|
||||
"version": "0.24.2",
|
||||
"description": "The Linux 64-bit binary for esbuild, a JavaScript bundler.",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/evanw/esbuild.git"
|
||||
},
|
||||
"license": "MIT",
|
||||
"preferUnplugged": true,
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"cpu": [
|
||||
"x64"
|
||||
]
|
||||
}
|
||||
21
frontend/lib/meshcore-cracker/node_modules/esbuild/LICENSE.md
generated
vendored
Normal file
21
frontend/lib/meshcore-cracker/node_modules/esbuild/LICENSE.md
generated
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2020 Evan Wallace
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
3
frontend/lib/meshcore-cracker/node_modules/esbuild/README.md
generated
vendored
Normal file
3
frontend/lib/meshcore-cracker/node_modules/esbuild/README.md
generated
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
# esbuild
|
||||
|
||||
This is a JavaScript bundler and minifier. See https://github.com/evanw/esbuild and the [JavaScript API documentation](https://esbuild.github.io/api/) for details.
|
||||
BIN
frontend/lib/meshcore-cracker/node_modules/esbuild/bin/esbuild
generated
vendored
Normal file
BIN
frontend/lib/meshcore-cracker/node_modules/esbuild/bin/esbuild
generated
vendored
Normal file
Binary file not shown.
287
frontend/lib/meshcore-cracker/node_modules/esbuild/install.js
generated
vendored
Normal file
287
frontend/lib/meshcore-cracker/node_modules/esbuild/install.js
generated
vendored
Normal file
@@ -0,0 +1,287 @@
|
||||
"use strict";
|
||||
var __create = Object.create;
|
||||
var __defProp = Object.defineProperty;
|
||||
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
||||
var __getOwnPropNames = Object.getOwnPropertyNames;
|
||||
var __getProtoOf = Object.getPrototypeOf;
|
||||
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
||||
var __copyProps = (to, from, except, desc) => {
|
||||
if (from && typeof from === "object" || typeof from === "function") {
|
||||
for (let key of __getOwnPropNames(from))
|
||||
if (!__hasOwnProp.call(to, key) && key !== except)
|
||||
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
||||
}
|
||||
return to;
|
||||
};
|
||||
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
||||
// If the importer is in node compatibility mode or this is not an ESM
|
||||
// file that has been converted to a CommonJS file using a Babel-
|
||||
// compatible transform (i.e. "__esModule" has not been set), then set
|
||||
// "default" to the CommonJS "module.exports" for node compatibility.
|
||||
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
||||
mod
|
||||
));
|
||||
|
||||
// lib/npm/node-platform.ts
|
||||
var fs = require("fs");
|
||||
var os = require("os");
|
||||
var path = require("path");
|
||||
var ESBUILD_BINARY_PATH = process.env.ESBUILD_BINARY_PATH || ESBUILD_BINARY_PATH;
|
||||
var isValidBinaryPath = (x) => !!x && x !== "/usr/bin/esbuild";
|
||||
var knownWindowsPackages = {
|
||||
"win32 arm64 LE": "@esbuild/win32-arm64",
|
||||
"win32 ia32 LE": "@esbuild/win32-ia32",
|
||||
"win32 x64 LE": "@esbuild/win32-x64"
|
||||
};
|
||||
var knownUnixlikePackages = {
|
||||
"aix ppc64 BE": "@esbuild/aix-ppc64",
|
||||
"android arm64 LE": "@esbuild/android-arm64",
|
||||
"darwin arm64 LE": "@esbuild/darwin-arm64",
|
||||
"darwin x64 LE": "@esbuild/darwin-x64",
|
||||
"freebsd arm64 LE": "@esbuild/freebsd-arm64",
|
||||
"freebsd x64 LE": "@esbuild/freebsd-x64",
|
||||
"linux arm LE": "@esbuild/linux-arm",
|
||||
"linux arm64 LE": "@esbuild/linux-arm64",
|
||||
"linux ia32 LE": "@esbuild/linux-ia32",
|
||||
"linux mips64el LE": "@esbuild/linux-mips64el",
|
||||
"linux ppc64 LE": "@esbuild/linux-ppc64",
|
||||
"linux riscv64 LE": "@esbuild/linux-riscv64",
|
||||
"linux s390x BE": "@esbuild/linux-s390x",
|
||||
"linux x64 LE": "@esbuild/linux-x64",
|
||||
"linux loong64 LE": "@esbuild/linux-loong64",
|
||||
"netbsd arm64 LE": "@esbuild/netbsd-arm64",
|
||||
"netbsd x64 LE": "@esbuild/netbsd-x64",
|
||||
"openbsd arm64 LE": "@esbuild/openbsd-arm64",
|
||||
"openbsd x64 LE": "@esbuild/openbsd-x64",
|
||||
"sunos x64 LE": "@esbuild/sunos-x64"
|
||||
};
|
||||
var knownWebAssemblyFallbackPackages = {
|
||||
"android arm LE": "@esbuild/android-arm",
|
||||
"android x64 LE": "@esbuild/android-x64"
|
||||
};
|
||||
function pkgAndSubpathForCurrentPlatform() {
|
||||
let pkg;
|
||||
let subpath;
|
||||
let isWASM = false;
|
||||
let platformKey = `${process.platform} ${os.arch()} ${os.endianness()}`;
|
||||
if (platformKey in knownWindowsPackages) {
|
||||
pkg = knownWindowsPackages[platformKey];
|
||||
subpath = "esbuild.exe";
|
||||
} else if (platformKey in knownUnixlikePackages) {
|
||||
pkg = knownUnixlikePackages[platformKey];
|
||||
subpath = "bin/esbuild";
|
||||
} else if (platformKey in knownWebAssemblyFallbackPackages) {
|
||||
pkg = knownWebAssemblyFallbackPackages[platformKey];
|
||||
subpath = "bin/esbuild";
|
||||
isWASM = true;
|
||||
} else {
|
||||
throw new Error(`Unsupported platform: ${platformKey}`);
|
||||
}
|
||||
return { pkg, subpath, isWASM };
|
||||
}
|
||||
function downloadedBinPath(pkg, subpath) {
|
||||
const esbuildLibDir = path.dirname(require.resolve("esbuild"));
|
||||
return path.join(esbuildLibDir, `downloaded-${pkg.replace("/", "-")}-${path.basename(subpath)}`);
|
||||
}
|
||||
|
||||
// lib/npm/node-install.ts
|
||||
var fs2 = require("fs");
|
||||
var os2 = require("os");
|
||||
var path2 = require("path");
|
||||
var zlib = require("zlib");
|
||||
var https = require("https");
|
||||
var child_process = require("child_process");
|
||||
var versionFromPackageJSON = require(path2.join(__dirname, "package.json")).version;
|
||||
var toPath = path2.join(__dirname, "bin", "esbuild");
|
||||
var isToPathJS = true;
|
||||
function validateBinaryVersion(...command) {
|
||||
command.push("--version");
|
||||
let stdout;
|
||||
try {
|
||||
stdout = child_process.execFileSync(command.shift(), command, {
|
||||
// Without this, this install script strangely crashes with the error
|
||||
// "EACCES: permission denied, write" but only on Ubuntu Linux when node is
|
||||
// installed from the Snap Store. This is not a problem when you download
|
||||
// the official version of node. The problem appears to be that stderr
|
||||
// (i.e. file descriptor 2) isn't writable?
|
||||
//
|
||||
// More info:
|
||||
// - https://snapcraft.io/ (what the Snap Store is)
|
||||
// - https://nodejs.org/dist/ (download the official version of node)
|
||||
// - https://github.com/evanw/esbuild/issues/1711#issuecomment-1027554035
|
||||
//
|
||||
stdio: "pipe"
|
||||
}).toString().trim();
|
||||
} catch (err) {
|
||||
if (os2.platform() === "darwin" && /_SecTrustEvaluateWithError/.test(err + "")) {
|
||||
let os3 = "this version of macOS";
|
||||
try {
|
||||
os3 = "macOS " + child_process.execFileSync("sw_vers", ["-productVersion"]).toString().trim();
|
||||
} catch {
|
||||
}
|
||||
throw new Error(`The "esbuild" package cannot be installed because ${os3} is too outdated.
|
||||
|
||||
The Go compiler (which esbuild relies on) no longer supports ${os3},
|
||||
which means the "esbuild" binary executable can't be run. You can either:
|
||||
|
||||
* Update your version of macOS to one that the Go compiler supports
|
||||
* Use the "esbuild-wasm" package instead of the "esbuild" package
|
||||
* Build esbuild yourself using an older version of the Go compiler
|
||||
`);
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
if (stdout !== versionFromPackageJSON) {
|
||||
throw new Error(`Expected ${JSON.stringify(versionFromPackageJSON)} but got ${JSON.stringify(stdout)}`);
|
||||
}
|
||||
}
|
||||
function isYarn() {
|
||||
const { npm_config_user_agent } = process.env;
|
||||
if (npm_config_user_agent) {
|
||||
return /\byarn\//.test(npm_config_user_agent);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
function fetch(url) {
|
||||
return new Promise((resolve, reject) => {
|
||||
https.get(url, (res) => {
|
||||
if ((res.statusCode === 301 || res.statusCode === 302) && res.headers.location)
|
||||
return fetch(res.headers.location).then(resolve, reject);
|
||||
if (res.statusCode !== 200)
|
||||
return reject(new Error(`Server responded with ${res.statusCode}`));
|
||||
let chunks = [];
|
||||
res.on("data", (chunk) => chunks.push(chunk));
|
||||
res.on("end", () => resolve(Buffer.concat(chunks)));
|
||||
}).on("error", reject);
|
||||
});
|
||||
}
|
||||
function extractFileFromTarGzip(buffer, subpath) {
|
||||
try {
|
||||
buffer = zlib.unzipSync(buffer);
|
||||
} catch (err) {
|
||||
throw new Error(`Invalid gzip data in archive: ${err && err.message || err}`);
|
||||
}
|
||||
let str = (i, n) => String.fromCharCode(...buffer.subarray(i, i + n)).replace(/\0.*$/, "");
|
||||
let offset = 0;
|
||||
subpath = `package/${subpath}`;
|
||||
while (offset < buffer.length) {
|
||||
let name = str(offset, 100);
|
||||
let size = parseInt(str(offset + 124, 12), 8);
|
||||
offset += 512;
|
||||
if (!isNaN(size)) {
|
||||
if (name === subpath) return buffer.subarray(offset, offset + size);
|
||||
offset += size + 511 & ~511;
|
||||
}
|
||||
}
|
||||
throw new Error(`Could not find ${JSON.stringify(subpath)} in archive`);
|
||||
}
|
||||
function installUsingNPM(pkg, subpath, binPath) {
|
||||
const env = { ...process.env, npm_config_global: void 0 };
|
||||
const esbuildLibDir = path2.dirname(require.resolve("esbuild"));
|
||||
const installDir = path2.join(esbuildLibDir, "npm-install");
|
||||
fs2.mkdirSync(installDir);
|
||||
try {
|
||||
fs2.writeFileSync(path2.join(installDir, "package.json"), "{}");
|
||||
child_process.execSync(
|
||||
`npm install --loglevel=error --prefer-offline --no-audit --progress=false ${pkg}@${versionFromPackageJSON}`,
|
||||
{ cwd: installDir, stdio: "pipe", env }
|
||||
);
|
||||
const installedBinPath = path2.join(installDir, "node_modules", pkg, subpath);
|
||||
fs2.renameSync(installedBinPath, binPath);
|
||||
} finally {
|
||||
try {
|
||||
removeRecursive(installDir);
|
||||
} catch {
|
||||
}
|
||||
}
|
||||
}
|
||||
function removeRecursive(dir) {
|
||||
for (const entry of fs2.readdirSync(dir)) {
|
||||
const entryPath = path2.join(dir, entry);
|
||||
let stats;
|
||||
try {
|
||||
stats = fs2.lstatSync(entryPath);
|
||||
} catch {
|
||||
continue;
|
||||
}
|
||||
if (stats.isDirectory()) removeRecursive(entryPath);
|
||||
else fs2.unlinkSync(entryPath);
|
||||
}
|
||||
fs2.rmdirSync(dir);
|
||||
}
|
||||
function applyManualBinaryPathOverride(overridePath) {
|
||||
const pathString = JSON.stringify(overridePath);
|
||||
fs2.writeFileSync(toPath, `#!/usr/bin/env node
|
||||
require('child_process').execFileSync(${pathString}, process.argv.slice(2), { stdio: 'inherit' });
|
||||
`);
|
||||
const libMain = path2.join(__dirname, "lib", "main.js");
|
||||
const code = fs2.readFileSync(libMain, "utf8");
|
||||
fs2.writeFileSync(libMain, `var ESBUILD_BINARY_PATH = ${pathString};
|
||||
${code}`);
|
||||
}
|
||||
function maybeOptimizePackage(binPath) {
|
||||
if (os2.platform() !== "win32" && !isYarn()) {
|
||||
const tempPath = path2.join(__dirname, "bin-esbuild");
|
||||
try {
|
||||
fs2.linkSync(binPath, tempPath);
|
||||
fs2.renameSync(tempPath, toPath);
|
||||
isToPathJS = false;
|
||||
fs2.unlinkSync(tempPath);
|
||||
} catch {
|
||||
}
|
||||
}
|
||||
}
|
||||
async function downloadDirectlyFromNPM(pkg, subpath, binPath) {
|
||||
const url = `https://registry.npmjs.org/${pkg}/-/${pkg.replace("@esbuild/", "")}-${versionFromPackageJSON}.tgz`;
|
||||
console.error(`[esbuild] Trying to download ${JSON.stringify(url)}`);
|
||||
try {
|
||||
fs2.writeFileSync(binPath, extractFileFromTarGzip(await fetch(url), subpath));
|
||||
fs2.chmodSync(binPath, 493);
|
||||
} catch (e) {
|
||||
console.error(`[esbuild] Failed to download ${JSON.stringify(url)}: ${e && e.message || e}`);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
async function checkAndPreparePackage() {
|
||||
if (isValidBinaryPath(ESBUILD_BINARY_PATH)) {
|
||||
if (!fs2.existsSync(ESBUILD_BINARY_PATH)) {
|
||||
console.warn(`[esbuild] Ignoring bad configuration: ESBUILD_BINARY_PATH=${ESBUILD_BINARY_PATH}`);
|
||||
} else {
|
||||
applyManualBinaryPathOverride(ESBUILD_BINARY_PATH);
|
||||
return;
|
||||
}
|
||||
}
|
||||
const { pkg, subpath } = pkgAndSubpathForCurrentPlatform();
|
||||
let binPath;
|
||||
try {
|
||||
binPath = require.resolve(`${pkg}/${subpath}`);
|
||||
} catch (e) {
|
||||
console.error(`[esbuild] Failed to find package "${pkg}" on the file system
|
||||
|
||||
This can happen if you use the "--no-optional" flag. The "optionalDependencies"
|
||||
package.json feature is used by esbuild to install the correct binary executable
|
||||
for your current platform. This install script will now attempt to work around
|
||||
this. If that fails, you need to remove the "--no-optional" flag to use esbuild.
|
||||
`);
|
||||
binPath = downloadedBinPath(pkg, subpath);
|
||||
try {
|
||||
console.error(`[esbuild] Trying to install package "${pkg}" using npm`);
|
||||
installUsingNPM(pkg, subpath, binPath);
|
||||
} catch (e2) {
|
||||
console.error(`[esbuild] Failed to install package "${pkg}" using npm: ${e2 && e2.message || e2}`);
|
||||
try {
|
||||
await downloadDirectlyFromNPM(pkg, subpath, binPath);
|
||||
} catch (e3) {
|
||||
throw new Error(`Failed to install package "${pkg}"`);
|
||||
}
|
||||
}
|
||||
}
|
||||
maybeOptimizePackage(binPath);
|
||||
}
|
||||
checkAndPreparePackage().then(() => {
|
||||
if (isToPathJS) {
|
||||
validateBinaryVersion(process.execPath, toPath);
|
||||
} else {
|
||||
validateBinaryVersion(toPath);
|
||||
}
|
||||
});
|
||||
705
frontend/lib/meshcore-cracker/node_modules/esbuild/lib/main.d.ts
generated
vendored
Normal file
705
frontend/lib/meshcore-cracker/node_modules/esbuild/lib/main.d.ts
generated
vendored
Normal file
@@ -0,0 +1,705 @@
|
||||
export type Platform = 'browser' | 'node' | 'neutral'
|
||||
export type Format = 'iife' | 'cjs' | 'esm'
|
||||
export type Loader = 'base64' | 'binary' | 'copy' | 'css' | 'dataurl' | 'default' | 'empty' | 'file' | 'js' | 'json' | 'jsx' | 'local-css' | 'text' | 'ts' | 'tsx'
|
||||
export type LogLevel = 'verbose' | 'debug' | 'info' | 'warning' | 'error' | 'silent'
|
||||
export type Charset = 'ascii' | 'utf8'
|
||||
export type Drop = 'console' | 'debugger'
|
||||
|
||||
interface CommonOptions {
|
||||
/** Documentation: https://esbuild.github.io/api/#sourcemap */
|
||||
sourcemap?: boolean | 'linked' | 'inline' | 'external' | 'both'
|
||||
/** Documentation: https://esbuild.github.io/api/#legal-comments */
|
||||
legalComments?: 'none' | 'inline' | 'eof' | 'linked' | 'external'
|
||||
/** Documentation: https://esbuild.github.io/api/#source-root */
|
||||
sourceRoot?: string
|
||||
/** Documentation: https://esbuild.github.io/api/#sources-content */
|
||||
sourcesContent?: boolean
|
||||
|
||||
/** Documentation: https://esbuild.github.io/api/#format */
|
||||
format?: Format
|
||||
/** Documentation: https://esbuild.github.io/api/#global-name */
|
||||
globalName?: string
|
||||
/** Documentation: https://esbuild.github.io/api/#target */
|
||||
target?: string | string[]
|
||||
/** Documentation: https://esbuild.github.io/api/#supported */
|
||||
supported?: Record<string, boolean>
|
||||
/** Documentation: https://esbuild.github.io/api/#platform */
|
||||
platform?: Platform
|
||||
|
||||
/** Documentation: https://esbuild.github.io/api/#mangle-props */
|
||||
mangleProps?: RegExp
|
||||
/** Documentation: https://esbuild.github.io/api/#mangle-props */
|
||||
reserveProps?: RegExp
|
||||
/** Documentation: https://esbuild.github.io/api/#mangle-props */
|
||||
mangleQuoted?: boolean
|
||||
/** Documentation: https://esbuild.github.io/api/#mangle-props */
|
||||
mangleCache?: Record<string, string | false>
|
||||
/** Documentation: https://esbuild.github.io/api/#drop */
|
||||
drop?: Drop[]
|
||||
/** Documentation: https://esbuild.github.io/api/#drop-labels */
|
||||
dropLabels?: string[]
|
||||
/** Documentation: https://esbuild.github.io/api/#minify */
|
||||
minify?: boolean
|
||||
/** Documentation: https://esbuild.github.io/api/#minify */
|
||||
minifyWhitespace?: boolean
|
||||
/** Documentation: https://esbuild.github.io/api/#minify */
|
||||
minifyIdentifiers?: boolean
|
||||
/** Documentation: https://esbuild.github.io/api/#minify */
|
||||
minifySyntax?: boolean
|
||||
/** Documentation: https://esbuild.github.io/api/#line-limit */
|
||||
lineLimit?: number
|
||||
/** Documentation: https://esbuild.github.io/api/#charset */
|
||||
charset?: Charset
|
||||
/** Documentation: https://esbuild.github.io/api/#tree-shaking */
|
||||
treeShaking?: boolean
|
||||
/** Documentation: https://esbuild.github.io/api/#ignore-annotations */
|
||||
ignoreAnnotations?: boolean
|
||||
|
||||
/** Documentation: https://esbuild.github.io/api/#jsx */
|
||||
jsx?: 'transform' | 'preserve' | 'automatic'
|
||||
/** Documentation: https://esbuild.github.io/api/#jsx-factory */
|
||||
jsxFactory?: string
|
||||
/** Documentation: https://esbuild.github.io/api/#jsx-fragment */
|
||||
jsxFragment?: string
|
||||
/** Documentation: https://esbuild.github.io/api/#jsx-import-source */
|
||||
jsxImportSource?: string
|
||||
/** Documentation: https://esbuild.github.io/api/#jsx-development */
|
||||
jsxDev?: boolean
|
||||
/** Documentation: https://esbuild.github.io/api/#jsx-side-effects */
|
||||
jsxSideEffects?: boolean
|
||||
|
||||
/** Documentation: https://esbuild.github.io/api/#define */
|
||||
define?: { [key: string]: string }
|
||||
/** Documentation: https://esbuild.github.io/api/#pure */
|
||||
pure?: string[]
|
||||
/** Documentation: https://esbuild.github.io/api/#keep-names */
|
||||
keepNames?: boolean
|
||||
|
||||
/** Documentation: https://esbuild.github.io/api/#color */
|
||||
color?: boolean
|
||||
/** Documentation: https://esbuild.github.io/api/#log-level */
|
||||
logLevel?: LogLevel
|
||||
/** Documentation: https://esbuild.github.io/api/#log-limit */
|
||||
logLimit?: number
|
||||
/** Documentation: https://esbuild.github.io/api/#log-override */
|
||||
logOverride?: Record<string, LogLevel>
|
||||
|
||||
/** Documentation: https://esbuild.github.io/api/#tsconfig-raw */
|
||||
tsconfigRaw?: string | TsconfigRaw
|
||||
}
|
||||
|
||||
export interface TsconfigRaw {
|
||||
compilerOptions?: {
|
||||
alwaysStrict?: boolean
|
||||
baseUrl?: string
|
||||
experimentalDecorators?: boolean
|
||||
importsNotUsedAsValues?: 'remove' | 'preserve' | 'error'
|
||||
jsx?: 'preserve' | 'react-native' | 'react' | 'react-jsx' | 'react-jsxdev'
|
||||
jsxFactory?: string
|
||||
jsxFragmentFactory?: string
|
||||
jsxImportSource?: string
|
||||
paths?: Record<string, string[]>
|
||||
preserveValueImports?: boolean
|
||||
strict?: boolean
|
||||
target?: string
|
||||
useDefineForClassFields?: boolean
|
||||
verbatimModuleSyntax?: boolean
|
||||
}
|
||||
}
|
||||
|
||||
export interface BuildOptions extends CommonOptions {
|
||||
/** Documentation: https://esbuild.github.io/api/#bundle */
|
||||
bundle?: boolean
|
||||
/** Documentation: https://esbuild.github.io/api/#splitting */
|
||||
splitting?: boolean
|
||||
/** Documentation: https://esbuild.github.io/api/#preserve-symlinks */
|
||||
preserveSymlinks?: boolean
|
||||
/** Documentation: https://esbuild.github.io/api/#outfile */
|
||||
outfile?: string
|
||||
/** Documentation: https://esbuild.github.io/api/#metafile */
|
||||
metafile?: boolean
|
||||
/** Documentation: https://esbuild.github.io/api/#outdir */
|
||||
outdir?: string
|
||||
/** Documentation: https://esbuild.github.io/api/#outbase */
|
||||
outbase?: string
|
||||
/** Documentation: https://esbuild.github.io/api/#external */
|
||||
external?: string[]
|
||||
/** Documentation: https://esbuild.github.io/api/#packages */
|
||||
packages?: 'bundle' | 'external'
|
||||
/** Documentation: https://esbuild.github.io/api/#alias */
|
||||
alias?: Record<string, string>
|
||||
/** Documentation: https://esbuild.github.io/api/#loader */
|
||||
loader?: { [ext: string]: Loader }
|
||||
/** Documentation: https://esbuild.github.io/api/#resolve-extensions */
|
||||
resolveExtensions?: string[]
|
||||
/** Documentation: https://esbuild.github.io/api/#main-fields */
|
||||
mainFields?: string[]
|
||||
/** Documentation: https://esbuild.github.io/api/#conditions */
|
||||
conditions?: string[]
|
||||
/** Documentation: https://esbuild.github.io/api/#write */
|
||||
write?: boolean
|
||||
/** Documentation: https://esbuild.github.io/api/#allow-overwrite */
|
||||
allowOverwrite?: boolean
|
||||
/** Documentation: https://esbuild.github.io/api/#tsconfig */
|
||||
tsconfig?: string
|
||||
/** Documentation: https://esbuild.github.io/api/#out-extension */
|
||||
outExtension?: { [ext: string]: string }
|
||||
/** Documentation: https://esbuild.github.io/api/#public-path */
|
||||
publicPath?: string
|
||||
/** Documentation: https://esbuild.github.io/api/#entry-names */
|
||||
entryNames?: string
|
||||
/** Documentation: https://esbuild.github.io/api/#chunk-names */
|
||||
chunkNames?: string
|
||||
/** Documentation: https://esbuild.github.io/api/#asset-names */
|
||||
assetNames?: string
|
||||
/** Documentation: https://esbuild.github.io/api/#inject */
|
||||
inject?: string[]
|
||||
/** Documentation: https://esbuild.github.io/api/#banner */
|
||||
banner?: { [type: string]: string }
|
||||
/** Documentation: https://esbuild.github.io/api/#footer */
|
||||
footer?: { [type: string]: string }
|
||||
/** Documentation: https://esbuild.github.io/api/#entry-points */
|
||||
entryPoints?: string[] | Record<string, string> | { in: string, out: string }[]
|
||||
/** Documentation: https://esbuild.github.io/api/#stdin */
|
||||
stdin?: StdinOptions
|
||||
/** Documentation: https://esbuild.github.io/plugins/ */
|
||||
plugins?: Plugin[]
|
||||
/** Documentation: https://esbuild.github.io/api/#working-directory */
|
||||
absWorkingDir?: string
|
||||
/** Documentation: https://esbuild.github.io/api/#node-paths */
|
||||
nodePaths?: string[]; // The "NODE_PATH" variable from Node.js
|
||||
}
|
||||
|
||||
export interface StdinOptions {
|
||||
contents: string | Uint8Array
|
||||
resolveDir?: string
|
||||
sourcefile?: string
|
||||
loader?: Loader
|
||||
}
|
||||
|
||||
export interface Message {
|
||||
id: string
|
||||
pluginName: string
|
||||
text: string
|
||||
location: Location | null
|
||||
notes: Note[]
|
||||
|
||||
/**
|
||||
* Optional user-specified data that is passed through unmodified. You can
|
||||
* use this to stash the original error, for example.
|
||||
*/
|
||||
detail: any
|
||||
}
|
||||
|
||||
export interface Note {
|
||||
text: string
|
||||
location: Location | null
|
||||
}
|
||||
|
||||
export interface Location {
|
||||
file: string
|
||||
namespace: string
|
||||
/** 1-based */
|
||||
line: number
|
||||
/** 0-based, in bytes */
|
||||
column: number
|
||||
/** in bytes */
|
||||
length: number
|
||||
lineText: string
|
||||
suggestion: string
|
||||
}
|
||||
|
||||
export interface OutputFile {
|
||||
path: string
|
||||
contents: Uint8Array
|
||||
hash: string
|
||||
/** "contents" as text (changes automatically with "contents") */
|
||||
readonly text: string
|
||||
}
|
||||
|
||||
export interface BuildResult<ProvidedOptions extends BuildOptions = BuildOptions> {
|
||||
errors: Message[]
|
||||
warnings: Message[]
|
||||
/** Only when "write: false" */
|
||||
outputFiles: OutputFile[] | (ProvidedOptions['write'] extends false ? never : undefined)
|
||||
/** Only when "metafile: true" */
|
||||
metafile: Metafile | (ProvidedOptions['metafile'] extends true ? never : undefined)
|
||||
/** Only when "mangleCache" is present */
|
||||
mangleCache: Record<string, string | false> | (ProvidedOptions['mangleCache'] extends Object ? never : undefined)
|
||||
}
|
||||
|
||||
export interface BuildFailure extends Error {
|
||||
errors: Message[]
|
||||
warnings: Message[]
|
||||
}
|
||||
|
||||
/** Documentation: https://esbuild.github.io/api/#serve-arguments */
|
||||
export interface ServeOptions {
|
||||
port?: number
|
||||
host?: string
|
||||
servedir?: string
|
||||
keyfile?: string
|
||||
certfile?: string
|
||||
fallback?: string
|
||||
onRequest?: (args: ServeOnRequestArgs) => void
|
||||
}
|
||||
|
||||
export interface ServeOnRequestArgs {
|
||||
remoteAddress: string
|
||||
method: string
|
||||
path: string
|
||||
status: number
|
||||
/** The time to generate the response, not to send it */
|
||||
timeInMS: number
|
||||
}
|
||||
|
||||
/** Documentation: https://esbuild.github.io/api/#serve-return-values */
|
||||
export interface ServeResult {
|
||||
port: number
|
||||
host: string
|
||||
}
|
||||
|
||||
export interface TransformOptions extends CommonOptions {
|
||||
/** Documentation: https://esbuild.github.io/api/#sourcefile */
|
||||
sourcefile?: string
|
||||
/** Documentation: https://esbuild.github.io/api/#loader */
|
||||
loader?: Loader
|
||||
/** Documentation: https://esbuild.github.io/api/#banner */
|
||||
banner?: string
|
||||
/** Documentation: https://esbuild.github.io/api/#footer */
|
||||
footer?: string
|
||||
}
|
||||
|
||||
export interface TransformResult<ProvidedOptions extends TransformOptions = TransformOptions> {
|
||||
code: string
|
||||
map: string
|
||||
warnings: Message[]
|
||||
/** Only when "mangleCache" is present */
|
||||
mangleCache: Record<string, string | false> | (ProvidedOptions['mangleCache'] extends Object ? never : undefined)
|
||||
/** Only when "legalComments" is "external" */
|
||||
legalComments: string | (ProvidedOptions['legalComments'] extends 'external' ? never : undefined)
|
||||
}
|
||||
|
||||
export interface TransformFailure extends Error {
|
||||
errors: Message[]
|
||||
warnings: Message[]
|
||||
}
|
||||
|
||||
export interface Plugin {
|
||||
name: string
|
||||
setup: (build: PluginBuild) => (void | Promise<void>)
|
||||
}
|
||||
|
||||
export interface PluginBuild {
|
||||
/** Documentation: https://esbuild.github.io/plugins/#build-options */
|
||||
initialOptions: BuildOptions
|
||||
|
||||
/** Documentation: https://esbuild.github.io/plugins/#resolve */
|
||||
resolve(path: string, options?: ResolveOptions): Promise<ResolveResult>
|
||||
|
||||
/** Documentation: https://esbuild.github.io/plugins/#on-start */
|
||||
onStart(callback: () =>
|
||||
(OnStartResult | null | void | Promise<OnStartResult | null | void>)): void
|
||||
|
||||
/** Documentation: https://esbuild.github.io/plugins/#on-end */
|
||||
onEnd(callback: (result: BuildResult) =>
|
||||
(OnEndResult | null | void | Promise<OnEndResult | null | void>)): void
|
||||
|
||||
/** Documentation: https://esbuild.github.io/plugins/#on-resolve */
|
||||
onResolve(options: OnResolveOptions, callback: (args: OnResolveArgs) =>
|
||||
(OnResolveResult | null | undefined | Promise<OnResolveResult | null | undefined>)): void
|
||||
|
||||
/** Documentation: https://esbuild.github.io/plugins/#on-load */
|
||||
onLoad(options: OnLoadOptions, callback: (args: OnLoadArgs) =>
|
||||
(OnLoadResult | null | undefined | Promise<OnLoadResult | null | undefined>)): void
|
||||
|
||||
/** Documentation: https://esbuild.github.io/plugins/#on-dispose */
|
||||
onDispose(callback: () => void): void
|
||||
|
||||
// This is a full copy of the esbuild library in case you need it
|
||||
esbuild: {
|
||||
context: typeof context,
|
||||
build: typeof build,
|
||||
buildSync: typeof buildSync,
|
||||
transform: typeof transform,
|
||||
transformSync: typeof transformSync,
|
||||
formatMessages: typeof formatMessages,
|
||||
formatMessagesSync: typeof formatMessagesSync,
|
||||
analyzeMetafile: typeof analyzeMetafile,
|
||||
analyzeMetafileSync: typeof analyzeMetafileSync,
|
||||
initialize: typeof initialize,
|
||||
version: typeof version,
|
||||
}
|
||||
}
|
||||
|
||||
/** Documentation: https://esbuild.github.io/plugins/#resolve-options */
|
||||
export interface ResolveOptions {
|
||||
pluginName?: string
|
||||
importer?: string
|
||||
namespace?: string
|
||||
resolveDir?: string
|
||||
kind?: ImportKind
|
||||
pluginData?: any
|
||||
with?: Record<string, string>
|
||||
}
|
||||
|
||||
/** Documentation: https://esbuild.github.io/plugins/#resolve-results */
|
||||
export interface ResolveResult {
|
||||
errors: Message[]
|
||||
warnings: Message[]
|
||||
|
||||
path: string
|
||||
external: boolean
|
||||
sideEffects: boolean
|
||||
namespace: string
|
||||
suffix: string
|
||||
pluginData: any
|
||||
}
|
||||
|
||||
export interface OnStartResult {
|
||||
errors?: PartialMessage[]
|
||||
warnings?: PartialMessage[]
|
||||
}
|
||||
|
||||
export interface OnEndResult {
|
||||
errors?: PartialMessage[]
|
||||
warnings?: PartialMessage[]
|
||||
}
|
||||
|
||||
/** Documentation: https://esbuild.github.io/plugins/#on-resolve-options */
|
||||
export interface OnResolveOptions {
|
||||
filter: RegExp
|
||||
namespace?: string
|
||||
}
|
||||
|
||||
/** Documentation: https://esbuild.github.io/plugins/#on-resolve-arguments */
|
||||
export interface OnResolveArgs {
|
||||
path: string
|
||||
importer: string
|
||||
namespace: string
|
||||
resolveDir: string
|
||||
kind: ImportKind
|
||||
pluginData: any
|
||||
with: Record<string, string>
|
||||
}
|
||||
|
||||
export type ImportKind =
|
||||
| 'entry-point'
|
||||
|
||||
// JS
|
||||
| 'import-statement'
|
||||
| 'require-call'
|
||||
| 'dynamic-import'
|
||||
| 'require-resolve'
|
||||
|
||||
// CSS
|
||||
| 'import-rule'
|
||||
| 'composes-from'
|
||||
| 'url-token'
|
||||
|
||||
/** Documentation: https://esbuild.github.io/plugins/#on-resolve-results */
|
||||
export interface OnResolveResult {
|
||||
pluginName?: string
|
||||
|
||||
errors?: PartialMessage[]
|
||||
warnings?: PartialMessage[]
|
||||
|
||||
path?: string
|
||||
external?: boolean
|
||||
sideEffects?: boolean
|
||||
namespace?: string
|
||||
suffix?: string
|
||||
pluginData?: any
|
||||
|
||||
watchFiles?: string[]
|
||||
watchDirs?: string[]
|
||||
}
|
||||
|
||||
/** Documentation: https://esbuild.github.io/plugins/#on-load-options */
|
||||
export interface OnLoadOptions {
|
||||
filter: RegExp
|
||||
namespace?: string
|
||||
}
|
||||
|
||||
/** Documentation: https://esbuild.github.io/plugins/#on-load-arguments */
|
||||
export interface OnLoadArgs {
|
||||
path: string
|
||||
namespace: string
|
||||
suffix: string
|
||||
pluginData: any
|
||||
with: Record<string, string>
|
||||
}
|
||||
|
||||
/** Documentation: https://esbuild.github.io/plugins/#on-load-results */
|
||||
export interface OnLoadResult {
|
||||
pluginName?: string
|
||||
|
||||
errors?: PartialMessage[]
|
||||
warnings?: PartialMessage[]
|
||||
|
||||
contents?: string | Uint8Array
|
||||
resolveDir?: string
|
||||
loader?: Loader
|
||||
pluginData?: any
|
||||
|
||||
watchFiles?: string[]
|
||||
watchDirs?: string[]
|
||||
}
|
||||
|
||||
export interface PartialMessage {
|
||||
id?: string
|
||||
pluginName?: string
|
||||
text?: string
|
||||
location?: Partial<Location> | null
|
||||
notes?: PartialNote[]
|
||||
detail?: any
|
||||
}
|
||||
|
||||
export interface PartialNote {
|
||||
text?: string
|
||||
location?: Partial<Location> | null
|
||||
}
|
||||
|
||||
/** Documentation: https://esbuild.github.io/api/#metafile */
|
||||
export interface Metafile {
|
||||
inputs: {
|
||||
[path: string]: {
|
||||
bytes: number
|
||||
imports: {
|
||||
path: string
|
||||
kind: ImportKind
|
||||
external?: boolean
|
||||
original?: string
|
||||
with?: Record<string, string>
|
||||
}[]
|
||||
format?: 'cjs' | 'esm'
|
||||
with?: Record<string, string>
|
||||
}
|
||||
}
|
||||
outputs: {
|
||||
[path: string]: {
|
||||
bytes: number
|
||||
inputs: {
|
||||
[path: string]: {
|
||||
bytesInOutput: number
|
||||
}
|
||||
}
|
||||
imports: {
|
||||
path: string
|
||||
kind: ImportKind | 'file-loader'
|
||||
external?: boolean
|
||||
}[]
|
||||
exports: string[]
|
||||
entryPoint?: string
|
||||
cssBundle?: string
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export interface FormatMessagesOptions {
|
||||
kind: 'error' | 'warning'
|
||||
color?: boolean
|
||||
terminalWidth?: number
|
||||
}
|
||||
|
||||
export interface AnalyzeMetafileOptions {
|
||||
color?: boolean
|
||||
verbose?: boolean
|
||||
}
|
||||
|
||||
export interface WatchOptions {
|
||||
}
|
||||
|
||||
export interface BuildContext<ProvidedOptions extends BuildOptions = BuildOptions> {
|
||||
/** Documentation: https://esbuild.github.io/api/#rebuild */
|
||||
rebuild(): Promise<BuildResult<ProvidedOptions>>
|
||||
|
||||
/** Documentation: https://esbuild.github.io/api/#watch */
|
||||
watch(options?: WatchOptions): Promise<void>
|
||||
|
||||
/** Documentation: https://esbuild.github.io/api/#serve */
|
||||
serve(options?: ServeOptions): Promise<ServeResult>
|
||||
|
||||
cancel(): Promise<void>
|
||||
dispose(): Promise<void>
|
||||
}
|
||||
|
||||
// This is a TypeScript type-level function which replaces any keys in "In"
|
||||
// that aren't in "Out" with "never". We use this to reject properties with
|
||||
// typos in object literals. See: https://stackoverflow.com/questions/49580725
|
||||
type SameShape<Out, In extends Out> = In & { [Key in Exclude<keyof In, keyof Out>]: never }
|
||||
|
||||
/**
|
||||
* This function invokes the "esbuild" command-line tool for you. It returns a
|
||||
* promise that either resolves with a "BuildResult" object or rejects with a
|
||||
* "BuildFailure" object.
|
||||
*
|
||||
* - Works in node: yes
|
||||
* - Works in browser: yes
|
||||
*
|
||||
* Documentation: https://esbuild.github.io/api/#build
|
||||
*/
|
||||
export declare function build<T extends BuildOptions>(options: SameShape<BuildOptions, T>): Promise<BuildResult<T>>
|
||||
|
||||
/**
|
||||
* This is the advanced long-running form of "build" that supports additional
|
||||
* features such as watch mode and a local development server.
|
||||
*
|
||||
* - Works in node: yes
|
||||
* - Works in browser: no
|
||||
*
|
||||
* Documentation: https://esbuild.github.io/api/#build
|
||||
*/
|
||||
export declare function context<T extends BuildOptions>(options: SameShape<BuildOptions, T>): Promise<BuildContext<T>>
|
||||
|
||||
/**
|
||||
* This function transforms a single JavaScript file. It can be used to minify
|
||||
* JavaScript, convert TypeScript/JSX to JavaScript, or convert newer JavaScript
|
||||
* to older JavaScript. It returns a promise that is either resolved with a
|
||||
* "TransformResult" object or rejected with a "TransformFailure" object.
|
||||
*
|
||||
* - Works in node: yes
|
||||
* - Works in browser: yes
|
||||
*
|
||||
* Documentation: https://esbuild.github.io/api/#transform
|
||||
*/
|
||||
export declare function transform<T extends TransformOptions>(input: string | Uint8Array, options?: SameShape<TransformOptions, T>): Promise<TransformResult<T>>
|
||||
|
||||
/**
|
||||
* Converts log messages to formatted message strings suitable for printing in
|
||||
* the terminal. This allows you to reuse the built-in behavior of esbuild's
|
||||
* log message formatter. This is a batch-oriented API for efficiency.
|
||||
*
|
||||
* - Works in node: yes
|
||||
* - Works in browser: yes
|
||||
*/
|
||||
export declare function formatMessages(messages: PartialMessage[], options: FormatMessagesOptions): Promise<string[]>
|
||||
|
||||
/**
|
||||
* Pretty-prints an analysis of the metafile JSON to a string. This is just for
|
||||
* convenience to be able to match esbuild's pretty-printing exactly. If you want
|
||||
* to customize it, you can just inspect the data in the metafile yourself.
|
||||
*
|
||||
* - Works in node: yes
|
||||
* - Works in browser: yes
|
||||
*
|
||||
* Documentation: https://esbuild.github.io/api/#analyze
|
||||
*/
|
||||
export declare function analyzeMetafile(metafile: Metafile | string, options?: AnalyzeMetafileOptions): Promise<string>
|
||||
|
||||
/**
|
||||
* A synchronous version of "build".
|
||||
*
|
||||
* - Works in node: yes
|
||||
* - Works in browser: no
|
||||
*
|
||||
* Documentation: https://esbuild.github.io/api/#build
|
||||
*/
|
||||
export declare function buildSync<T extends BuildOptions>(options: SameShape<BuildOptions, T>): BuildResult<T>
|
||||
|
||||
/**
|
||||
* A synchronous version of "transform".
|
||||
*
|
||||
* - Works in node: yes
|
||||
* - Works in browser: no
|
||||
*
|
||||
* Documentation: https://esbuild.github.io/api/#transform
|
||||
*/
|
||||
export declare function transformSync<T extends TransformOptions>(input: string | Uint8Array, options?: SameShape<TransformOptions, T>): TransformResult<T>
|
||||
|
||||
/**
|
||||
* A synchronous version of "formatMessages".
|
||||
*
|
||||
* - Works in node: yes
|
||||
* - Works in browser: no
|
||||
*/
|
||||
export declare function formatMessagesSync(messages: PartialMessage[], options: FormatMessagesOptions): string[]
|
||||
|
||||
/**
|
||||
* A synchronous version of "analyzeMetafile".
|
||||
*
|
||||
* - Works in node: yes
|
||||
* - Works in browser: no
|
||||
*
|
||||
* Documentation: https://esbuild.github.io/api/#analyze
|
||||
*/
|
||||
export declare function analyzeMetafileSync(metafile: Metafile | string, options?: AnalyzeMetafileOptions): string
|
||||
|
||||
/**
|
||||
* This configures the browser-based version of esbuild. It is necessary to
|
||||
* call this first and wait for the returned promise to be resolved before
|
||||
* making other API calls when using esbuild in the browser.
|
||||
*
|
||||
* - Works in node: yes
|
||||
* - Works in browser: yes ("options" is required)
|
||||
*
|
||||
* Documentation: https://esbuild.github.io/api/#browser
|
||||
*/
|
||||
export declare function initialize(options: InitializeOptions): Promise<void>
|
||||
|
||||
export interface InitializeOptions {
|
||||
/**
|
||||
* The URL of the "esbuild.wasm" file. This must be provided when running
|
||||
* esbuild in the browser.
|
||||
*/
|
||||
wasmURL?: string | URL
|
||||
|
||||
/**
|
||||
* The result of calling "new WebAssembly.Module(buffer)" where "buffer"
|
||||
* is a typed array or ArrayBuffer containing the binary code of the
|
||||
* "esbuild.wasm" file.
|
||||
*
|
||||
* You can use this as an alternative to "wasmURL" for environments where it's
|
||||
* not possible to download the WebAssembly module.
|
||||
*/
|
||||
wasmModule?: WebAssembly.Module
|
||||
|
||||
/**
|
||||
* By default esbuild runs the WebAssembly-based browser API in a web worker
|
||||
* to avoid blocking the UI thread. This can be disabled by setting "worker"
|
||||
* to false.
|
||||
*/
|
||||
worker?: boolean
|
||||
}
|
||||
|
||||
export let version: string
|
||||
|
||||
// Call this function to terminate esbuild's child process. The child process
|
||||
// is not terminated and re-created after each API call because it's more
|
||||
// efficient to keep it around when there are multiple API calls.
|
||||
//
|
||||
// In node this happens automatically before the parent node process exits. So
|
||||
// you only need to call this if you know you will not make any more esbuild
|
||||
// API calls and you want to clean up resources.
|
||||
//
|
||||
// Unlike node, Deno lacks the necessary APIs to clean up child processes
|
||||
// automatically. You must manually call stop() in Deno when you're done
|
||||
// using esbuild or Deno will continue running forever.
|
||||
//
|
||||
// Another reason you might want to call this is if you are using esbuild from
|
||||
// within a Deno test. Deno fails tests that create a child process without
|
||||
// killing it before the test ends, so you have to call this function (and
|
||||
// await the returned promise) in every Deno test that uses esbuild.
|
||||
export declare function stop(): Promise<void>
|
||||
|
||||
// Note: These declarations exist to avoid type errors when you omit "dom" from
|
||||
// "lib" in your "tsconfig.json" file. TypeScript confusingly declares the
|
||||
// global "WebAssembly" type in "lib.dom.d.ts" even though it has nothing to do
|
||||
// with the browser DOM and is present in many non-browser JavaScript runtimes
|
||||
// (e.g. node and deno). Declaring it here allows esbuild's API to be used in
|
||||
// these scenarios.
|
||||
//
|
||||
// There's an open issue about getting this problem corrected (although these
|
||||
// declarations will need to remain even if this is fixed for backward
|
||||
// compatibility with older TypeScript versions):
|
||||
//
|
||||
// https://github.com/microsoft/TypeScript-DOM-lib-generator/issues/826
|
||||
//
|
||||
declare global {
|
||||
namespace WebAssembly {
|
||||
interface Module {
|
||||
}
|
||||
}
|
||||
interface URL {
|
||||
}
|
||||
}
|
||||
2245
frontend/lib/meshcore-cracker/node_modules/esbuild/lib/main.js
generated
vendored
Normal file
2245
frontend/lib/meshcore-cracker/node_modules/esbuild/lib/main.js
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
48
frontend/lib/meshcore-cracker/node_modules/esbuild/package.json
generated
vendored
Normal file
48
frontend/lib/meshcore-cracker/node_modules/esbuild/package.json
generated
vendored
Normal file
@@ -0,0 +1,48 @@
|
||||
{
|
||||
"name": "esbuild",
|
||||
"version": "0.24.2",
|
||||
"description": "An extremely fast JavaScript and CSS bundler and minifier.",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/evanw/esbuild.git"
|
||||
},
|
||||
"scripts": {
|
||||
"postinstall": "node install.js"
|
||||
},
|
||||
"main": "lib/main.js",
|
||||
"types": "lib/main.d.ts",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"bin": {
|
||||
"esbuild": "bin/esbuild"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@esbuild/aix-ppc64": "0.24.2",
|
||||
"@esbuild/android-arm": "0.24.2",
|
||||
"@esbuild/android-arm64": "0.24.2",
|
||||
"@esbuild/android-x64": "0.24.2",
|
||||
"@esbuild/darwin-arm64": "0.24.2",
|
||||
"@esbuild/darwin-x64": "0.24.2",
|
||||
"@esbuild/freebsd-arm64": "0.24.2",
|
||||
"@esbuild/freebsd-x64": "0.24.2",
|
||||
"@esbuild/linux-arm": "0.24.2",
|
||||
"@esbuild/linux-arm64": "0.24.2",
|
||||
"@esbuild/linux-ia32": "0.24.2",
|
||||
"@esbuild/linux-loong64": "0.24.2",
|
||||
"@esbuild/linux-mips64el": "0.24.2",
|
||||
"@esbuild/linux-ppc64": "0.24.2",
|
||||
"@esbuild/linux-riscv64": "0.24.2",
|
||||
"@esbuild/linux-s390x": "0.24.2",
|
||||
"@esbuild/linux-x64": "0.24.2",
|
||||
"@esbuild/netbsd-arm64": "0.24.2",
|
||||
"@esbuild/netbsd-x64": "0.24.2",
|
||||
"@esbuild/openbsd-arm64": "0.24.2",
|
||||
"@esbuild/openbsd-x64": "0.24.2",
|
||||
"@esbuild/sunos-x64": "0.24.2",
|
||||
"@esbuild/win32-arm64": "0.24.2",
|
||||
"@esbuild/win32-ia32": "0.24.2",
|
||||
"@esbuild/win32-x64": "0.24.2"
|
||||
},
|
||||
"license": "MIT"
|
||||
}
|
||||
38
frontend/lib/meshcore-cracker/package.json
Normal file
38
frontend/lib/meshcore-cracker/package.json
Normal file
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"name": "meshcore-cracker",
|
||||
"version": "1.0.0",
|
||||
"description": "Standalone MeshCore GroupText packet cracker library",
|
||||
"type": "module",
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
"exports": {
|
||||
".": {
|
||||
"import": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts"
|
||||
}
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "tsc && node build.js",
|
||||
"watch": "tsc --watch"
|
||||
},
|
||||
"dependencies": {
|
||||
"@michaelhart/meshcore-decoder": "^0.2.7",
|
||||
"crypto-js": "^4.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/crypto-js": "^4.2.2",
|
||||
"@webgpu/types": "^0.1.68",
|
||||
"esbuild": "^0.24.2",
|
||||
"typescript": "^5.7.2"
|
||||
},
|
||||
"keywords": [
|
||||
"meshcore",
|
||||
"cracker",
|
||||
"brute-force",
|
||||
"webgpu"
|
||||
],
|
||||
"license": "MIT"
|
||||
}
|
||||
282
frontend/lib/meshcore-cracker/src/core.ts
Normal file
282
frontend/lib/meshcore-cracker/src/core.ts
Normal file
@@ -0,0 +1,282 @@
|
||||
// Core logic for MeshCore packet cracker - pure functions
|
||||
|
||||
import SHA256 from 'crypto-js/sha256';
|
||||
import HmacSHA256 from 'crypto-js/hmac-sha256';
|
||||
import Hex from 'crypto-js/enc-hex';
|
||||
|
||||
// Room name character set
|
||||
export const CHARS = 'abcdefghijklmnopqrstuvwxyz0123456789';
|
||||
export const CHARS_LEN = CHARS.length; // 36
|
||||
export const CHARS_WITH_DASH = CHARS + '-';
|
||||
|
||||
// Public room special case
|
||||
export const PUBLIC_ROOM_NAME = '[[public room]]';
|
||||
export const PUBLIC_KEY = '8b3387e9c5cdea6ac9e5edbaa115cd72';
|
||||
|
||||
/**
|
||||
* Convert room name to (length, index) for resuming/skipping.
|
||||
* Index encoding: LSB-first (first character = least significant digit).
|
||||
*/
|
||||
export function roomNameToIndex(name: string): { length: number; index: number } | null {
|
||||
if (!name || name.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const length = name.length;
|
||||
let index = 0;
|
||||
let multiplier = 1;
|
||||
|
||||
// Process from left to right (first char is LSB, matching indexToRoomName)
|
||||
for (let i = 0; i < length; i++) {
|
||||
const c = name[i];
|
||||
const charIdx = CHARS_WITH_DASH.indexOf(c);
|
||||
if (charIdx === -1) {
|
||||
return null;
|
||||
} // Invalid character
|
||||
|
||||
const isFirst = i === 0;
|
||||
const isLast = i === length - 1;
|
||||
const charCount = isFirst || isLast ? 36 : 37;
|
||||
|
||||
// Dash not allowed at start/end
|
||||
if ((isFirst || isLast) && charIdx === 36) {
|
||||
return null;
|
||||
}
|
||||
|
||||
index += charIdx * multiplier;
|
||||
multiplier *= charCount;
|
||||
}
|
||||
|
||||
return { length, index };
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert (length, index) to room name.
|
||||
* Index encoding: LSB-first (first character = least significant digit).
|
||||
*/
|
||||
export function indexToRoomName(length: number, idx: number): string | null {
|
||||
if (length <= 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let result = '';
|
||||
let remaining = idx;
|
||||
let prevWasDash = false;
|
||||
|
||||
for (let i = 0; i < length; i++) {
|
||||
const isFirst = i === 0;
|
||||
const isLast = i === length - 1;
|
||||
const charCount = isFirst || isLast ? 36 : 37;
|
||||
const charIdx = remaining % charCount;
|
||||
remaining = Math.floor(remaining / charCount);
|
||||
|
||||
const isDash = charIdx === 36;
|
||||
if (isDash && prevWasDash) {
|
||||
return null;
|
||||
} // Invalid: consecutive dashes
|
||||
prevWasDash = isDash;
|
||||
|
||||
result += CHARS_WITH_DASH[charIdx];
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Derive 128-bit key from room name using SHA256.
|
||||
* Room names are prefixed with '#' before hashing.
|
||||
*/
|
||||
export function deriveKeyFromRoomName(roomName: string): string {
|
||||
if (roomName === PUBLIC_ROOM_NAME) {
|
||||
return PUBLIC_KEY;
|
||||
}
|
||||
const hash = SHA256(roomName);
|
||||
return hash.toString(Hex).substring(0, 32);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute channel hash (first byte of SHA256(key)).
|
||||
*/
|
||||
export function getChannelHash(keyHex: string): string {
|
||||
const hash = SHA256(Hex.parse(keyHex));
|
||||
return hash.toString(Hex).substring(0, 2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify MAC using HMAC-SHA256 with 32-byte padded key.
|
||||
*/
|
||||
export function verifyMac(ciphertext: string, cipherMac: string, keyHex: string): boolean {
|
||||
const paddedKey = keyHex.padEnd(64, '0');
|
||||
const hmac = HmacSHA256(Hex.parse(ciphertext), Hex.parse(paddedKey));
|
||||
const computed = hmac.toString(Hex).substring(0, 4).toLowerCase();
|
||||
return computed === cipherMac.toLowerCase();
|
||||
}
|
||||
|
||||
/**
|
||||
* Count valid room names for a given length.
|
||||
* Accounts for dash rules (no start/end dash, no consecutive dashes).
|
||||
*/
|
||||
export function countNamesForLength(len: number): number {
|
||||
if (len === 1) {
|
||||
return CHARS_LEN;
|
||||
}
|
||||
if (len === 2) {
|
||||
return CHARS_LEN * CHARS_LEN;
|
||||
}
|
||||
|
||||
// For length >= 3: first and last are CHARS (36), middle follows no-consecutive-dash rule
|
||||
// Middle length = len - 2
|
||||
// Use DP: count sequences of length k with no consecutive dashes
|
||||
// endsWithNonDash[k], endsWithDash[k]
|
||||
let endsNonDash = CHARS_LEN; // length 1 middle
|
||||
let endsDash = 1;
|
||||
|
||||
for (let i = 2; i <= len - 2; i++) {
|
||||
const newEndsNonDash = (endsNonDash + endsDash) * CHARS_LEN;
|
||||
const newEndsDash = endsNonDash; // dash can only follow non-dash
|
||||
endsNonDash = newEndsNonDash;
|
||||
endsDash = newEndsDash;
|
||||
}
|
||||
|
||||
const middleCount = len > 2 ? endsNonDash + endsDash : 1;
|
||||
return CHARS_LEN * middleCount * CHARS_LEN;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if timestamp is within last month.
|
||||
*/
|
||||
export function isTimestampValid(timestamp: number, now?: number): boolean {
|
||||
const ONE_MONTH_SECONDS = 30 * 24 * 60 * 60;
|
||||
const currentTime = now ?? Math.floor(Date.now() / 1000);
|
||||
return timestamp <= currentTime && timestamp >= currentTime - ONE_MONTH_SECONDS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check for valid UTF-8 (no replacement characters).
|
||||
*/
|
||||
export function isValidUtf8(text: string): boolean {
|
||||
return !text.includes('\uFFFD');
|
||||
}
|
||||
|
||||
/**
|
||||
* Room name generator - iterates through all valid room names.
|
||||
*/
|
||||
export class RoomNameGenerator {
|
||||
private length = 1;
|
||||
private indices: number[] = [0];
|
||||
private done = false;
|
||||
private currentInLength = 0;
|
||||
private totalForLength = CHARS_LEN;
|
||||
|
||||
current(): string {
|
||||
return this.indices.map((i) => (i === CHARS_LEN ? '-' : CHARS[i])).join('');
|
||||
}
|
||||
|
||||
getLength(): number {
|
||||
return this.length;
|
||||
}
|
||||
|
||||
getCurrentInLength(): number {
|
||||
return this.currentInLength;
|
||||
}
|
||||
|
||||
getTotalForLength(): number {
|
||||
return this.totalForLength;
|
||||
}
|
||||
|
||||
getRemainingInLength(): number {
|
||||
return this.totalForLength - this.currentInLength;
|
||||
}
|
||||
|
||||
isDone(): boolean {
|
||||
return this.done;
|
||||
}
|
||||
|
||||
next(): boolean {
|
||||
if (this.done) {
|
||||
return false;
|
||||
}
|
||||
|
||||
this.currentInLength++;
|
||||
|
||||
// Increment with carry, respecting dash rules
|
||||
let pos = this.length - 1;
|
||||
|
||||
while (pos >= 0) {
|
||||
const isFirst = pos === 0;
|
||||
const isLast = pos === this.length - 1;
|
||||
const maxVal = isFirst || isLast ? CHARS_LEN - 1 : CHARS_LEN; // CHARS_LEN = dash index
|
||||
|
||||
if (this.indices[pos] < maxVal) {
|
||||
this.indices[pos]++;
|
||||
// Check dash rule: no consecutive dashes
|
||||
if (this.indices[pos] === CHARS_LEN && pos > 0 && this.indices[pos - 1] === CHARS_LEN) {
|
||||
// Would create consecutive dashes, continue incrementing
|
||||
continue;
|
||||
}
|
||||
// Reset all positions after this one
|
||||
for (let i = pos + 1; i < this.length; i++) {
|
||||
this.indices[i] = 0;
|
||||
}
|
||||
// Validate: check no consecutive dashes in reset portion
|
||||
if (this.isValid()) {
|
||||
return true;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
pos--;
|
||||
}
|
||||
|
||||
// Overflow - increase length
|
||||
this.length++;
|
||||
this.indices = new Array(this.length).fill(0);
|
||||
this.currentInLength = 0;
|
||||
this.totalForLength = countNamesForLength(this.length);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private isValid(): boolean {
|
||||
for (let i = 0; i < this.length; i++) {
|
||||
const isDash = this.indices[i] === CHARS_LEN;
|
||||
if (isDash && (i === 0 || i === this.length - 1)) {
|
||||
return false;
|
||||
}
|
||||
if (isDash && i > 0 && this.indices[i - 1] === CHARS_LEN) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Skip invalid combinations efficiently
|
||||
nextValid(): boolean {
|
||||
do {
|
||||
if (!this.next()) {
|
||||
return false;
|
||||
}
|
||||
} while (!this.isValid());
|
||||
return true;
|
||||
}
|
||||
|
||||
// Skip to a specific (length, index) position
|
||||
// Index encoding: first char is LSB (consistent with indexToRoomName)
|
||||
skipTo(targetLength: number, targetIndex: number): void {
|
||||
this.length = targetLength;
|
||||
this.indices = new Array(targetLength).fill(0);
|
||||
this.totalForLength = countNamesForLength(targetLength);
|
||||
|
||||
// Convert index to indices array (LSB first = position 0)
|
||||
let remaining = targetIndex;
|
||||
for (let i = 0; i < targetLength; i++) {
|
||||
const isFirst = i === 0;
|
||||
const isLast = i === targetLength - 1;
|
||||
const charCount = isFirst || isLast ? CHARS_LEN : CHARS_LEN + 1;
|
||||
this.indices[i] = remaining % charCount;
|
||||
remaining = Math.floor(remaining / charCount);
|
||||
}
|
||||
|
||||
this.currentInLength = targetIndex;
|
||||
}
|
||||
}
|
||||
403
frontend/lib/meshcore-cracker/src/cracker.ts
Normal file
403
frontend/lib/meshcore-cracker/src/cracker.ts
Normal file
@@ -0,0 +1,403 @@
|
||||
/**
|
||||
* GroupTextCracker - Standalone MeshCore GroupText packet cracker
|
||||
*
|
||||
* Cracks encrypted GroupText packets by trying room names until the
|
||||
* correct encryption key is found.
|
||||
*/
|
||||
|
||||
import { MeshCorePacketDecoder, ChannelCrypto } from '@michaelhart/meshcore-decoder';
|
||||
import { GpuBruteForce, isWebGpuSupported } from './gpu-bruteforce';
|
||||
import {
|
||||
PUBLIC_ROOM_NAME,
|
||||
PUBLIC_KEY,
|
||||
indexToRoomName,
|
||||
roomNameToIndex,
|
||||
deriveKeyFromRoomName,
|
||||
getChannelHash,
|
||||
verifyMac,
|
||||
countNamesForLength,
|
||||
isTimestampValid,
|
||||
isValidUtf8,
|
||||
} from './core';
|
||||
import type { CrackOptions, CrackResult, ProgressReport, ProgressCallback, DecodedPacket } from './types';
|
||||
|
||||
// Valid room name characters (for wordlist filtering)
|
||||
const VALID_CHARS = /^[a-z0-9-]+$/;
|
||||
const NO_DASH_AT_ENDS = /^[a-z0-9].*[a-z0-9]$|^[a-z0-9]$/;
|
||||
const NO_CONSECUTIVE_DASHES = /--/;
|
||||
|
||||
function isValidRoomName(name: string): boolean {
|
||||
if (!name || name.length === 0) return false;
|
||||
if (!VALID_CHARS.test(name)) return false;
|
||||
if (name.length > 1 && !NO_DASH_AT_ENDS.test(name)) return false;
|
||||
if (NO_CONSECUTIVE_DASHES.test(name)) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Main cracker class for MeshCore GroupText packets.
|
||||
*/
|
||||
export class GroupTextCracker {
|
||||
private gpuInstance: GpuBruteForce | null = null;
|
||||
private wordlist: string[] = [];
|
||||
private abortFlag = false;
|
||||
private useTimestampFilter = true;
|
||||
private useUtf8Filter = true;
|
||||
|
||||
/**
|
||||
* Load a wordlist from a URL for dictionary attacks.
|
||||
* The wordlist should be a text file with one word per line.
|
||||
*
|
||||
* @param url - URL to fetch the wordlist from
|
||||
*/
|
||||
async loadWordlist(url: string): Promise<void> {
|
||||
const response = await fetch(url);
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to load wordlist: ${response.status} ${response.statusText}`);
|
||||
}
|
||||
|
||||
const text = await response.text();
|
||||
const allWords = text
|
||||
.split('\n')
|
||||
.map((w) => w.trim().toLowerCase())
|
||||
.filter((w) => w.length > 0);
|
||||
|
||||
// Filter to valid room names only
|
||||
this.wordlist = allWords.filter(isValidRoomName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the wordlist directly from an array of words.
|
||||
*
|
||||
* @param words - Array of room names to try
|
||||
*/
|
||||
setWordlist(words: string[]): void {
|
||||
this.wordlist = words
|
||||
.map((w) => w.trim().toLowerCase())
|
||||
.filter(isValidRoomName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Abort the current cracking operation.
|
||||
* The crack() method will return with aborted: true.
|
||||
*/
|
||||
abort(): void {
|
||||
this.abortFlag = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if WebGPU is available in the current environment.
|
||||
*/
|
||||
isGpuAvailable(): boolean {
|
||||
return isWebGpuSupported();
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode a packet and extract the information needed for cracking.
|
||||
*
|
||||
* @param packetHex - The packet data as a hex string
|
||||
* @returns Decoded packet info or null if not a GroupText packet
|
||||
*/
|
||||
async decodePacket(packetHex: string): Promise<DecodedPacket | null> {
|
||||
const cleanHex = packetHex.trim().replace(/\s+/g, '').replace(/^0x/i, '');
|
||||
|
||||
if (!cleanHex || !/^[0-9a-fA-F]+$/.test(cleanHex)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
const decoded = await MeshCorePacketDecoder.decodeWithVerification(cleanHex, {});
|
||||
const payload = decoded.payload?.decoded as {
|
||||
channelHash?: string;
|
||||
ciphertext?: string;
|
||||
cipherMac?: string;
|
||||
} | null;
|
||||
|
||||
if (!payload?.channelHash || !payload?.ciphertext || !payload?.cipherMac) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
channelHash: payload.channelHash,
|
||||
ciphertext: payload.ciphertext,
|
||||
cipherMac: payload.cipherMac,
|
||||
isGroupText: true,
|
||||
};
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Crack a GroupText packet to find the room name and decrypt the message.
|
||||
*
|
||||
* @param packetHex - The packet data as a hex string
|
||||
* @param options - Cracking options
|
||||
* @param onProgress - Optional callback for progress updates
|
||||
* @returns The cracking result
|
||||
*/
|
||||
async crack(
|
||||
packetHex: string,
|
||||
options?: CrackOptions,
|
||||
onProgress?: ProgressCallback,
|
||||
): Promise<CrackResult> {
|
||||
this.abortFlag = false;
|
||||
this.useTimestampFilter = options?.useTimestampFilter ?? true;
|
||||
this.useUtf8Filter = options?.useUtf8Filter ?? true;
|
||||
const maxLength = options?.maxLength ?? 8;
|
||||
|
||||
// Decode packet
|
||||
const decoded = await this.decodePacket(packetHex);
|
||||
if (!decoded) {
|
||||
return { found: false, error: 'Invalid packet or not a GroupText packet' };
|
||||
}
|
||||
|
||||
const { channelHash, ciphertext, cipherMac } = decoded;
|
||||
const targetHashByte = parseInt(channelHash, 16);
|
||||
|
||||
// Initialize GPU if not already done
|
||||
if (!this.gpuInstance) {
|
||||
this.gpuInstance = new GpuBruteForce();
|
||||
const gpuOk = await this.gpuInstance.init();
|
||||
if (!gpuOk) {
|
||||
return { found: false, error: 'WebGPU not available' };
|
||||
}
|
||||
}
|
||||
|
||||
const startTime = performance.now();
|
||||
let totalChecked = 0;
|
||||
let lastProgressUpdate = performance.now();
|
||||
|
||||
// Determine starting position
|
||||
let startFromLength = 1;
|
||||
let startFromOffset = 0;
|
||||
if (options?.startFrom) {
|
||||
const pos = roomNameToIndex(options.startFrom);
|
||||
if (pos) {
|
||||
startFromLength = pos.length;
|
||||
startFromOffset = pos.index + 1; // Start after the given position
|
||||
if (startFromOffset >= countNamesForLength(startFromLength)) {
|
||||
startFromLength++;
|
||||
startFromOffset = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate total candidates for progress
|
||||
let totalCandidates = 0;
|
||||
for (let l = startFromLength; l <= maxLength; l++) {
|
||||
totalCandidates += countNamesForLength(l);
|
||||
}
|
||||
totalCandidates -= startFromOffset;
|
||||
|
||||
// Helper to report progress
|
||||
const reportProgress = (
|
||||
phase: ProgressReport['phase'],
|
||||
currentLength: number,
|
||||
currentPosition: string,
|
||||
) => {
|
||||
if (!onProgress) return;
|
||||
|
||||
const now = performance.now();
|
||||
const elapsed = (now - startTime) / 1000;
|
||||
const rate = elapsed > 0 ? Math.round(totalChecked / elapsed) : 0;
|
||||
const remaining = totalCandidates - totalChecked;
|
||||
const eta = rate > 0 ? remaining / rate : 0;
|
||||
|
||||
onProgress({
|
||||
checked: totalChecked,
|
||||
total: totalCandidates,
|
||||
percent: totalCandidates > 0 ? Math.min(100, (totalChecked / totalCandidates) * 100) : 0,
|
||||
rateKeysPerSec: rate,
|
||||
etaSeconds: eta,
|
||||
elapsedSeconds: elapsed,
|
||||
currentLength,
|
||||
currentPosition,
|
||||
phase,
|
||||
});
|
||||
};
|
||||
|
||||
// Helper to verify MAC and filters
|
||||
const verifyMacAndFilters = (
|
||||
key: string,
|
||||
): { valid: boolean; message?: string } => {
|
||||
if (!verifyMac(ciphertext, cipherMac, key)) {
|
||||
return { valid: false };
|
||||
}
|
||||
|
||||
const result = ChannelCrypto.decryptGroupTextMessage(ciphertext, cipherMac, key);
|
||||
if (!result.success || !result.data) {
|
||||
return { valid: false };
|
||||
}
|
||||
|
||||
if (this.useTimestampFilter && !isTimestampValid(result.data.timestamp)) {
|
||||
return { valid: false };
|
||||
}
|
||||
|
||||
if (this.useUtf8Filter && !isValidUtf8(result.data.message)) {
|
||||
return { valid: false };
|
||||
}
|
||||
|
||||
return { valid: true, message: result.data.message };
|
||||
};
|
||||
|
||||
// Phase 1: Try public key
|
||||
if (startFromLength === 1 && startFromOffset === 0) {
|
||||
reportProgress('public-key', 0, PUBLIC_ROOM_NAME);
|
||||
|
||||
const publicChannelHash = getChannelHash(PUBLIC_KEY);
|
||||
if (channelHash === publicChannelHash) {
|
||||
const result = verifyMacAndFilters(PUBLIC_KEY);
|
||||
if (result.valid) {
|
||||
return {
|
||||
found: true,
|
||||
roomName: PUBLIC_ROOM_NAME,
|
||||
key: PUBLIC_KEY,
|
||||
decryptedMessage: result.message,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Phase 2: Dictionary attack
|
||||
if (this.wordlist.length > 0 && startFromLength === 1 && startFromOffset === 0) {
|
||||
for (let i = 0; i < this.wordlist.length; i++) {
|
||||
if (this.abortFlag) {
|
||||
return {
|
||||
found: false,
|
||||
aborted: true,
|
||||
resumeFrom: this.wordlist[i],
|
||||
};
|
||||
}
|
||||
|
||||
const word = this.wordlist[i];
|
||||
const key = deriveKeyFromRoomName('#' + word);
|
||||
const wordChannelHash = getChannelHash(key);
|
||||
|
||||
if (parseInt(wordChannelHash, 16) === targetHashByte) {
|
||||
const result = verifyMacAndFilters(key);
|
||||
if (result.valid) {
|
||||
return {
|
||||
found: true,
|
||||
roomName: word,
|
||||
key,
|
||||
decryptedMessage: result.message,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Progress update
|
||||
const now = performance.now();
|
||||
if (now - lastProgressUpdate >= 200) {
|
||||
reportProgress('wordlist', word.length, word);
|
||||
lastProgressUpdate = now;
|
||||
await new Promise((resolve) => setTimeout(resolve, 0));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Phase 3: GPU brute force
|
||||
const INITIAL_BATCH_SIZE = 32768;
|
||||
const TARGET_DISPATCH_MS = 1000;
|
||||
let currentBatchSize = INITIAL_BATCH_SIZE;
|
||||
let batchSizeTuned = false;
|
||||
|
||||
for (let length = startFromLength; length <= maxLength; length++) {
|
||||
if (this.abortFlag) {
|
||||
const resumePos = indexToRoomName(length, 0);
|
||||
return {
|
||||
found: false,
|
||||
aborted: true,
|
||||
resumeFrom: resumePos || undefined,
|
||||
};
|
||||
}
|
||||
|
||||
const totalForLength = countNamesForLength(length);
|
||||
let offset = length === startFromLength ? startFromOffset : 0;
|
||||
|
||||
while (offset < totalForLength) {
|
||||
if (this.abortFlag) {
|
||||
const resumePos = indexToRoomName(length, offset);
|
||||
return {
|
||||
found: false,
|
||||
aborted: true,
|
||||
resumeFrom: resumePos || undefined,
|
||||
};
|
||||
}
|
||||
|
||||
const batchSize = Math.min(currentBatchSize, totalForLength - offset);
|
||||
const dispatchStart = performance.now();
|
||||
|
||||
const matches = await this.gpuInstance.runBatch(
|
||||
targetHashByte,
|
||||
length,
|
||||
offset,
|
||||
batchSize,
|
||||
ciphertext,
|
||||
cipherMac,
|
||||
);
|
||||
|
||||
const dispatchTime = performance.now() - dispatchStart;
|
||||
totalChecked += batchSize;
|
||||
|
||||
// Auto-tune batch size
|
||||
if (!batchSizeTuned && batchSize >= INITIAL_BATCH_SIZE && dispatchTime > 0) {
|
||||
const scaleFactor = TARGET_DISPATCH_MS / dispatchTime;
|
||||
const optimalBatchSize = Math.round(batchSize * scaleFactor);
|
||||
const rounded = Math.pow(
|
||||
2,
|
||||
Math.round(Math.log2(Math.max(INITIAL_BATCH_SIZE, optimalBatchSize))),
|
||||
);
|
||||
currentBatchSize = Math.max(INITIAL_BATCH_SIZE, rounded);
|
||||
batchSizeTuned = true;
|
||||
}
|
||||
|
||||
// Check matches
|
||||
for (const matchIdx of matches) {
|
||||
const roomName = indexToRoomName(length, matchIdx);
|
||||
if (!roomName) continue;
|
||||
|
||||
const key = deriveKeyFromRoomName('#' + roomName);
|
||||
const result = verifyMacAndFilters(key);
|
||||
if (result.valid) {
|
||||
return {
|
||||
found: true,
|
||||
roomName,
|
||||
key,
|
||||
decryptedMessage: result.message,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
offset += batchSize;
|
||||
|
||||
// Progress update
|
||||
const now = performance.now();
|
||||
if (now - lastProgressUpdate >= 200) {
|
||||
const currentPos = indexToRoomName(length, Math.min(offset, totalForLength - 1)) || '';
|
||||
reportProgress('bruteforce', length, currentPos);
|
||||
lastProgressUpdate = now;
|
||||
await new Promise((resolve) => setTimeout(resolve, 0));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Not found
|
||||
const lastPos = indexToRoomName(maxLength, countNamesForLength(maxLength) - 1);
|
||||
return {
|
||||
found: false,
|
||||
resumeFrom: lastPos || undefined,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean up GPU resources.
|
||||
* Call this when you're done using the cracker.
|
||||
*/
|
||||
destroy(): void {
|
||||
if (this.gpuInstance) {
|
||||
this.gpuInstance.destroy();
|
||||
this.gpuInstance = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
708
frontend/lib/meshcore-cracker/src/gpu-bruteforce.ts
Normal file
708
frontend/lib/meshcore-cracker/src/gpu-bruteforce.ts
Normal file
@@ -0,0 +1,708 @@
|
||||
// WebGPU-accelerated brute force key cracking for MeshCore packets
|
||||
|
||||
import { indexToRoomName, countNamesForLength } from './core';
|
||||
|
||||
export interface GpuBruteForceResult {
|
||||
found: boolean;
|
||||
roomName?: string;
|
||||
key?: string;
|
||||
candidateIndices?: number[];
|
||||
}
|
||||
|
||||
export class GpuBruteForce {
|
||||
private device: GPUDevice | null = null;
|
||||
private pipeline: GPUComputePipeline | null = null;
|
||||
private bindGroupLayout: GPUBindGroupLayout | null = null;
|
||||
|
||||
// Persistent buffers for reuse between batches
|
||||
private paramsBuffer: GPUBuffer | null = null;
|
||||
private matchCountBuffer: GPUBuffer | null = null;
|
||||
private matchIndicesBuffer: GPUBuffer | null = null;
|
||||
private ciphertextBuffer: GPUBuffer | null = null;
|
||||
private ciphertextBufferSize: number = 0;
|
||||
|
||||
// Double-buffered staging buffers for overlapping GPU/CPU work
|
||||
private matchCountReadBuffers: [GPUBuffer | null, GPUBuffer | null] = [null, null];
|
||||
private matchIndicesReadBuffers: [GPUBuffer | null, GPUBuffer | null] = [null, null];
|
||||
private currentReadBufferIndex: number = 0;
|
||||
|
||||
// Cached bind group (recreated only when ciphertext buffer changes)
|
||||
private bindGroup: GPUBindGroup | null = null;
|
||||
private bindGroupDirty: boolean = true;
|
||||
|
||||
// Reusable zero buffer for resetting match count
|
||||
private static readonly ZERO_DATA = new Uint32Array([0]);
|
||||
|
||||
// Shader for SHA256 computation
|
||||
private shaderCode = /* wgsl */ `
|
||||
// SHA256 round constants
|
||||
const K: array<u32, 64> = array<u32, 64>(
|
||||
0x428a2f98u, 0x71374491u, 0xb5c0fbcfu, 0xe9b5dba5u, 0x3956c25bu, 0x59f111f1u, 0x923f82a4u, 0xab1c5ed5u,
|
||||
0xd807aa98u, 0x12835b01u, 0x243185beu, 0x550c7dc3u, 0x72be5d74u, 0x80deb1feu, 0x9bdc06a7u, 0xc19bf174u,
|
||||
0xe49b69c1u, 0xefbe4786u, 0x0fc19dc6u, 0x240ca1ccu, 0x2de92c6fu, 0x4a7484aau, 0x5cb0a9dcu, 0x76f988dau,
|
||||
0x983e5152u, 0xa831c66du, 0xb00327c8u, 0xbf597fc7u, 0xc6e00bf3u, 0xd5a79147u, 0x06ca6351u, 0x14292967u,
|
||||
0x27b70a85u, 0x2e1b2138u, 0x4d2c6dfcu, 0x53380d13u, 0x650a7354u, 0x766a0abbu, 0x81c2c92eu, 0x92722c85u,
|
||||
0xa2bfe8a1u, 0xa81a664bu, 0xc24b8b70u, 0xc76c51a3u, 0xd192e819u, 0xd6990624u, 0xf40e3585u, 0x106aa070u,
|
||||
0x19a4c116u, 0x1e376c08u, 0x2748774cu, 0x34b0bcb5u, 0x391c0cb3u, 0x4ed8aa4au, 0x5b9cca4fu, 0x682e6ff3u,
|
||||
0x748f82eeu, 0x78a5636fu, 0x84c87814u, 0x8cc70208u, 0x90befffau, 0xa4506cebu, 0xbef9a3f7u, 0xc67178f2u
|
||||
);
|
||||
|
||||
// Character lookup table (a-z = 0-25, 0-9 = 26-35, dash = 36)
|
||||
const CHARS: array<u32, 37> = array<u32, 37>(
|
||||
0x61u, 0x62u, 0x63u, 0x64u, 0x65u, 0x66u, 0x67u, 0x68u, 0x69u, 0x6au, // a-j
|
||||
0x6bu, 0x6cu, 0x6du, 0x6eu, 0x6fu, 0x70u, 0x71u, 0x72u, 0x73u, 0x74u, // k-t
|
||||
0x75u, 0x76u, 0x77u, 0x78u, 0x79u, 0x7au, // u-z
|
||||
0x30u, 0x31u, 0x32u, 0x33u, 0x34u, 0x35u, 0x36u, 0x37u, 0x38u, 0x39u, // 0-9
|
||||
0x2du // dash
|
||||
);
|
||||
|
||||
struct Params {
|
||||
target_channel_hash: u32,
|
||||
batch_offset: u32,
|
||||
name_length: u32,
|
||||
batch_size: u32,
|
||||
target_mac: u32, // First 2 bytes of target MAC (in high 16 bits)
|
||||
ciphertext_words: u32, // Number of 32-bit words in ciphertext
|
||||
ciphertext_len_bits: u32, // Length of ciphertext in bits
|
||||
verify_mac: u32, // 1 to verify MAC, 0 to skip
|
||||
}
|
||||
|
||||
@group(0) @binding(0) var<uniform> params: Params;
|
||||
@group(0) @binding(1) var<storage, read_write> match_count: atomic<u32>;
|
||||
@group(0) @binding(2) var<storage, read_write> match_indices: array<u32>;
|
||||
@group(0) @binding(3) var<storage, read> ciphertext: array<u32>; // Ciphertext data
|
||||
|
||||
fn rotr(x: u32, n: u32) -> u32 {
|
||||
return (x >> n) | (x << (32u - n));
|
||||
}
|
||||
|
||||
fn ch(x: u32, y: u32, z: u32) -> u32 {
|
||||
return (x & y) ^ (~x & z);
|
||||
}
|
||||
|
||||
fn maj(x: u32, y: u32, z: u32) -> u32 {
|
||||
return (x & y) ^ (x & z) ^ (y & z);
|
||||
}
|
||||
|
||||
fn sigma0(x: u32) -> u32 {
|
||||
return rotr(x, 2u) ^ rotr(x, 13u) ^ rotr(x, 22u);
|
||||
}
|
||||
|
||||
fn sigma1(x: u32) -> u32 {
|
||||
return rotr(x, 6u) ^ rotr(x, 11u) ^ rotr(x, 25u);
|
||||
}
|
||||
|
||||
fn gamma0(x: u32) -> u32 {
|
||||
return rotr(x, 7u) ^ rotr(x, 18u) ^ (x >> 3u);
|
||||
}
|
||||
|
||||
fn gamma1(x: u32) -> u32 {
|
||||
return rotr(x, 17u) ^ rotr(x, 19u) ^ (x >> 10u);
|
||||
}
|
||||
|
||||
// Convert index to room name bytes, returns the hash as a u32 for the first byte check
|
||||
fn index_to_room_name(idx: u32, length: u32, msg: ptr<function, array<u32, 16>>) -> bool {
|
||||
// Message starts with '#' (0x23)
|
||||
var byte_pos = 0u;
|
||||
var word_idx = 0u;
|
||||
var current_word = 0x23000000u; // '#' in big-endian position 0
|
||||
byte_pos = 1u;
|
||||
|
||||
var remaining = idx;
|
||||
var prev_was_dash = false;
|
||||
|
||||
// Generate room name from index
|
||||
for (var i = 0u; i < length; i++) {
|
||||
let char_count = select(37u, 36u, i == 0u || i == length - 1u); // no dash at start/end
|
||||
var char_idx = remaining % char_count;
|
||||
remaining = remaining / char_count;
|
||||
|
||||
// Check for consecutive dashes (invalid)
|
||||
let is_dash = char_idx == 36u && i > 0u && i < length - 1u;
|
||||
if (is_dash && prev_was_dash) {
|
||||
return false; // Invalid: consecutive dashes
|
||||
}
|
||||
prev_was_dash = is_dash;
|
||||
|
||||
// Map char index to actual character
|
||||
let c = CHARS[char_idx];
|
||||
|
||||
// Pack byte into current word (big-endian)
|
||||
let shift = (3u - byte_pos % 4u) * 8u;
|
||||
if (byte_pos % 4u == 0u && byte_pos > 0u) {
|
||||
(*msg)[word_idx] = current_word;
|
||||
word_idx = word_idx + 1u;
|
||||
current_word = 0u;
|
||||
}
|
||||
current_word = current_word | (c << shift);
|
||||
byte_pos = byte_pos + 1u;
|
||||
}
|
||||
|
||||
// Add padding: 0x80 followed by zeros, then length in bits
|
||||
let msg_len_bits = (length + 1u) * 8u; // +1 for '#'
|
||||
|
||||
// Add 0x80 padding byte
|
||||
let shift = (3u - byte_pos % 4u) * 8u;
|
||||
if (byte_pos % 4u == 0u) {
|
||||
(*msg)[word_idx] = current_word;
|
||||
word_idx = word_idx + 1u;
|
||||
current_word = 0x80000000u;
|
||||
} else {
|
||||
current_word = current_word | (0x80u << shift);
|
||||
}
|
||||
byte_pos = byte_pos + 1u;
|
||||
|
||||
// Store current word
|
||||
if (byte_pos % 4u == 0u || word_idx < 14u) {
|
||||
(*msg)[word_idx] = current_word;
|
||||
word_idx = word_idx + 1u;
|
||||
}
|
||||
|
||||
// Zero-fill until word 14
|
||||
for (var i = word_idx; i < 14u; i++) {
|
||||
(*msg)[i] = 0u;
|
||||
}
|
||||
|
||||
// Length in bits (64-bit, but we only use lower 32 bits for short messages)
|
||||
(*msg)[14u] = 0u;
|
||||
(*msg)[15u] = msg_len_bits;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
fn sha256_block(msg: ptr<function, array<u32, 16>>) -> array<u32, 8> {
|
||||
// Initialize hash values
|
||||
var h: array<u32, 8> = array<u32, 8>(
|
||||
0x6a09e667u, 0xbb67ae85u, 0x3c6ef372u, 0xa54ff53au,
|
||||
0x510e527fu, 0x9b05688cu, 0x1f83d9abu, 0x5be0cd19u
|
||||
);
|
||||
|
||||
// Message schedule
|
||||
var w: array<u32, 64>;
|
||||
for (var i = 0u; i < 16u; i++) {
|
||||
w[i] = (*msg)[i];
|
||||
}
|
||||
for (var i = 16u; i < 64u; i++) {
|
||||
w[i] = gamma1(w[i-2u]) + w[i-7u] + gamma0(w[i-15u]) + w[i-16u];
|
||||
}
|
||||
|
||||
// Compression
|
||||
var a = h[0]; var b = h[1]; var c = h[2]; var d = h[3];
|
||||
var e = h[4]; var f = h[5]; var g = h[6]; var hh = h[7];
|
||||
|
||||
for (var i = 0u; i < 64u; i++) {
|
||||
let t1 = hh + sigma1(e) + ch(e, f, g) + K[i] + w[i];
|
||||
let t2 = sigma0(a) + maj(a, b, c);
|
||||
hh = g; g = f; f = e; e = d + t1;
|
||||
d = c; c = b; b = a; a = t1 + t2;
|
||||
}
|
||||
|
||||
h[0] = h[0] + a; h[1] = h[1] + b; h[2] = h[2] + c; h[3] = h[3] + d;
|
||||
h[4] = h[4] + e; h[5] = h[5] + f; h[6] = h[6] + g; h[7] = h[7] + hh;
|
||||
|
||||
return h;
|
||||
}
|
||||
|
||||
// Compute SHA256 of the key (16 bytes) to get channel hash
|
||||
fn sha256_key(key: array<u32, 4>) -> u32 {
|
||||
var msg: array<u32, 16>;
|
||||
|
||||
// Key bytes (16 bytes = 4 words)
|
||||
msg[0] = key[0];
|
||||
msg[1] = key[1];
|
||||
msg[2] = key[2];
|
||||
msg[3] = key[3];
|
||||
|
||||
// Padding: 0x80 followed by zeros
|
||||
msg[4] = 0x80000000u;
|
||||
for (var i = 5u; i < 14u; i++) {
|
||||
msg[i] = 0u;
|
||||
}
|
||||
|
||||
// Length: 128 bits
|
||||
msg[14] = 0u;
|
||||
msg[15] = 128u;
|
||||
|
||||
let hash = sha256_block(&msg);
|
||||
|
||||
// Return first byte of hash (big-endian)
|
||||
return hash[0] >> 24u;
|
||||
}
|
||||
|
||||
// HMAC-SHA256 for MAC verification
|
||||
// Key is 16 bytes (4 words), padded to 32 bytes with zeros for MeshCore
|
||||
// Returns first 2 bytes of HMAC (as u32 in high 16 bits)
|
||||
fn hmac_sha256_mac(key: array<u32, 4>, ciphertext_len: u32) -> u32 {
|
||||
// HMAC: H((K' ^ opad) || H((K' ^ ipad) || message))
|
||||
// K' is 64 bytes (32 bytes key + 32 bytes zero padding for MeshCore, then padded to 64)
|
||||
// ipad = 0x36 repeated, opad = 0x5c repeated
|
||||
|
||||
// Build padded key (64 bytes = 16 words)
|
||||
// MeshCore uses 32-byte secret: 16-byte key + 16 zero bytes
|
||||
var k_pad: array<u32, 16>;
|
||||
k_pad[0] = key[0];
|
||||
k_pad[1] = key[1];
|
||||
k_pad[2] = key[2];
|
||||
k_pad[3] = key[3];
|
||||
for (var i = 4u; i < 16u; i++) {
|
||||
k_pad[i] = 0u;
|
||||
}
|
||||
|
||||
// Inner hash: SHA256((K' ^ ipad) || message)
|
||||
// First block: K' ^ ipad (64 bytes)
|
||||
var inner_block: array<u32, 16>;
|
||||
for (var i = 0u; i < 16u; i++) {
|
||||
inner_block[i] = k_pad[i] ^ 0x36363636u;
|
||||
}
|
||||
|
||||
// Initialize hash state with first block
|
||||
var h: array<u32, 8> = sha256_block(&inner_block);
|
||||
|
||||
// Process ciphertext blocks (continuing from h state)
|
||||
let ciphertext_words = params.ciphertext_words;
|
||||
var word_idx = 0u;
|
||||
|
||||
// Process full 64-byte blocks of ciphertext
|
||||
while (word_idx + 16u <= ciphertext_words) {
|
||||
var block: array<u32, 16>;
|
||||
for (var i = 0u; i < 16u; i++) {
|
||||
block[i] = ciphertext[word_idx + i];
|
||||
}
|
||||
h = sha256_block_continue(&block, h);
|
||||
word_idx = word_idx + 16u;
|
||||
}
|
||||
|
||||
// Final block with remaining ciphertext + padding
|
||||
var final_block: array<u32, 16>;
|
||||
var remaining = ciphertext_words - word_idx;
|
||||
for (var i = 0u; i < 16u; i++) {
|
||||
if (i < remaining) {
|
||||
final_block[i] = ciphertext[word_idx + i];
|
||||
} else if (i == remaining) {
|
||||
// Add 0x80 padding
|
||||
final_block[i] = 0x80000000u;
|
||||
} else {
|
||||
final_block[i] = 0u;
|
||||
}
|
||||
}
|
||||
|
||||
// Add length (64 bytes of ipad + ciphertext length)
|
||||
let total_bits = 512u + params.ciphertext_len_bits;
|
||||
if (remaining < 14u) {
|
||||
final_block[14] = 0u;
|
||||
final_block[15] = total_bits;
|
||||
h = sha256_block_continue(&final_block, h);
|
||||
} else {
|
||||
// Need extra block for length
|
||||
h = sha256_block_continue(&final_block, h);
|
||||
var len_block: array<u32, 16>;
|
||||
for (var i = 0u; i < 14u; i++) {
|
||||
len_block[i] = 0u;
|
||||
}
|
||||
len_block[14] = 0u;
|
||||
len_block[15] = total_bits;
|
||||
h = sha256_block_continue(&len_block, h);
|
||||
}
|
||||
|
||||
let inner_hash = h;
|
||||
|
||||
// Outer hash: SHA256((K' ^ opad) || inner_hash)
|
||||
var outer_block: array<u32, 16>;
|
||||
for (var i = 0u; i < 16u; i++) {
|
||||
outer_block[i] = k_pad[i] ^ 0x5c5c5c5cu;
|
||||
}
|
||||
h = sha256_block(&outer_block);
|
||||
|
||||
// Second block: inner_hash (32 bytes) + padding
|
||||
var hash_block: array<u32, 16>;
|
||||
for (var i = 0u; i < 8u; i++) {
|
||||
hash_block[i] = inner_hash[i];
|
||||
}
|
||||
hash_block[8] = 0x80000000u;
|
||||
for (var i = 9u; i < 14u; i++) {
|
||||
hash_block[i] = 0u;
|
||||
}
|
||||
hash_block[14] = 0u;
|
||||
hash_block[15] = 512u + 256u; // 64 bytes opad + 32 bytes inner hash
|
||||
|
||||
h = sha256_block_continue(&hash_block, h);
|
||||
|
||||
// Return first 2 bytes (high 16 bits of first word)
|
||||
return h[0] & 0xFFFF0000u;
|
||||
}
|
||||
|
||||
// SHA256 block computation continuing from existing state
|
||||
fn sha256_block_continue(msg: ptr<function, array<u32, 16>>, h_in: array<u32, 8>) -> array<u32, 8> {
|
||||
var h = h_in;
|
||||
|
||||
// Message schedule
|
||||
var w: array<u32, 64>;
|
||||
for (var i = 0u; i < 16u; i++) {
|
||||
w[i] = (*msg)[i];
|
||||
}
|
||||
for (var i = 16u; i < 64u; i++) {
|
||||
w[i] = gamma1(w[i-2u]) + w[i-7u] + gamma0(w[i-15u]) + w[i-16u];
|
||||
}
|
||||
|
||||
// Compression
|
||||
var a = h[0]; var b = h[1]; var c = h[2]; var d = h[3];
|
||||
var e = h[4]; var f = h[5]; var g = h[6]; var hh = h[7];
|
||||
|
||||
for (var i = 0u; i < 64u; i++) {
|
||||
let t1 = hh + sigma1(e) + ch(e, f, g) + K[i] + w[i];
|
||||
let t2 = sigma0(a) + maj(a, b, c);
|
||||
hh = g; g = f; f = e; e = d + t1;
|
||||
d = c; c = b; b = a; a = t1 + t2;
|
||||
}
|
||||
|
||||
h[0] = h[0] + a; h[1] = h[1] + b; h[2] = h[2] + c; h[3] = h[3] + d;
|
||||
h[4] = h[4] + e; h[5] = h[5] + f; h[6] = h[6] + g; h[7] = h[7] + hh;
|
||||
|
||||
return h;
|
||||
}
|
||||
|
||||
// Process a single candidate and record match if found
|
||||
fn process_candidate(name_idx: u32) {
|
||||
// Generate message for this room name
|
||||
var msg: array<u32, 16>;
|
||||
let valid = index_to_room_name(name_idx, params.name_length, &msg);
|
||||
|
||||
if (!valid) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Compute SHA256("#roomname") - this gives us the key
|
||||
let key_hash = sha256_block(&msg);
|
||||
|
||||
// Take first 16 bytes (4 words) as the key
|
||||
var key: array<u32, 4>;
|
||||
key[0] = key_hash[0];
|
||||
key[1] = key_hash[1];
|
||||
key[2] = key_hash[2];
|
||||
key[3] = key_hash[3];
|
||||
|
||||
// Compute SHA256(key) to get channel hash
|
||||
let channel_hash = sha256_key(key);
|
||||
|
||||
// Check if channel hash matches target
|
||||
if (channel_hash != params.target_channel_hash) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Channel hash matches - verify MAC if enabled
|
||||
if (params.verify_mac == 1u) {
|
||||
let computed_mac = hmac_sha256_mac(key, params.ciphertext_len_bits);
|
||||
if (computed_mac != params.target_mac) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Found a match - record the index
|
||||
let match_idx = atomicAdd(&match_count, 1u);
|
||||
if (match_idx < 1024u) { // Limit stored matches
|
||||
match_indices[match_idx] = name_idx;
|
||||
}
|
||||
}
|
||||
|
||||
// Each thread processes 16 candidates to amortize thread overhead
|
||||
const CANDIDATES_PER_THREAD: u32 = 16u;
|
||||
|
||||
@compute @workgroup_size(256)
|
||||
fn main(@builtin(global_invocation_id) global_id: vec3<u32>) {
|
||||
let base_idx = global_id.x * CANDIDATES_PER_THREAD;
|
||||
|
||||
for (var i = 0u; i < CANDIDATES_PER_THREAD; i++) {
|
||||
let idx = base_idx + i;
|
||||
if (idx >= params.batch_size) {
|
||||
return;
|
||||
}
|
||||
let name_idx = params.batch_offset + idx;
|
||||
process_candidate(name_idx);
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
async init(): Promise<boolean> {
|
||||
if (!navigator.gpu) {
|
||||
console.warn('WebGPU not supported');
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
const adapter = await navigator.gpu.requestAdapter();
|
||||
if (!adapter) {
|
||||
console.warn('No GPU adapter found');
|
||||
return false;
|
||||
}
|
||||
|
||||
this.device = await adapter.requestDevice();
|
||||
|
||||
// Create bind group layout
|
||||
this.bindGroupLayout = this.device.createBindGroupLayout({
|
||||
entries: [
|
||||
{ binding: 0, visibility: GPUShaderStage.COMPUTE, buffer: { type: 'uniform' } },
|
||||
{ binding: 1, visibility: GPUShaderStage.COMPUTE, buffer: { type: 'storage' } },
|
||||
{ binding: 2, visibility: GPUShaderStage.COMPUTE, buffer: { type: 'storage' } },
|
||||
{ binding: 3, visibility: GPUShaderStage.COMPUTE, buffer: { type: 'read-only-storage' } },
|
||||
],
|
||||
});
|
||||
|
||||
// Create persistent buffers
|
||||
this.paramsBuffer = this.device.createBuffer({
|
||||
size: 32, // 8 u32s
|
||||
usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
|
||||
});
|
||||
|
||||
this.matchCountBuffer = this.device.createBuffer({
|
||||
size: 4,
|
||||
usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST,
|
||||
});
|
||||
|
||||
this.matchIndicesBuffer = this.device.createBuffer({
|
||||
size: 1024 * 4, // Max 1024 matches per batch
|
||||
usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC,
|
||||
});
|
||||
|
||||
// Double-buffered staging buffers
|
||||
for (let i = 0; i < 2; i++) {
|
||||
this.matchCountReadBuffers[i] = this.device.createBuffer({
|
||||
size: 4,
|
||||
usage: GPUBufferUsage.MAP_READ | GPUBufferUsage.COPY_DST,
|
||||
});
|
||||
|
||||
this.matchIndicesReadBuffers[i] = this.device.createBuffer({
|
||||
size: 1024 * 4,
|
||||
usage: GPUBufferUsage.MAP_READ | GPUBufferUsage.COPY_DST,
|
||||
});
|
||||
}
|
||||
|
||||
// Create pipeline
|
||||
const shaderModule = this.device.createShaderModule({
|
||||
code: this.shaderCode,
|
||||
});
|
||||
|
||||
const pipelineLayout = this.device.createPipelineLayout({
|
||||
bindGroupLayouts: [this.bindGroupLayout],
|
||||
});
|
||||
|
||||
this.pipeline = this.device.createComputePipeline({
|
||||
layout: pipelineLayout,
|
||||
compute: {
|
||||
module: shaderModule,
|
||||
entryPoint: 'main',
|
||||
},
|
||||
});
|
||||
|
||||
return true;
|
||||
} catch (e) {
|
||||
console.error('WebGPU initialization failed:', e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
isAvailable(): boolean {
|
||||
return this.device !== null && this.pipeline !== null;
|
||||
}
|
||||
|
||||
// Convert room name index to actual room name string (delegates to core)
|
||||
indexToRoomName(idx: number, length: number): string | null {
|
||||
return indexToRoomName(length, idx);
|
||||
}
|
||||
|
||||
// Count valid names for a given length (delegates to core)
|
||||
countNamesForLength(len: number): number {
|
||||
return countNamesForLength(len);
|
||||
}
|
||||
|
||||
async runBatch(
|
||||
targetChannelHash: number,
|
||||
nameLength: number,
|
||||
batchOffset: number,
|
||||
batchSize: number,
|
||||
ciphertextHex?: string,
|
||||
targetMacHex?: string,
|
||||
): Promise<number[]> {
|
||||
if (
|
||||
!this.device ||
|
||||
!this.pipeline ||
|
||||
!this.bindGroupLayout ||
|
||||
!this.paramsBuffer ||
|
||||
!this.matchCountBuffer ||
|
||||
!this.matchIndicesBuffer ||
|
||||
!this.matchCountReadBuffers[0] ||
|
||||
!this.matchCountReadBuffers[1] ||
|
||||
!this.matchIndicesReadBuffers[0] ||
|
||||
!this.matchIndicesReadBuffers[1]
|
||||
) {
|
||||
throw new Error('GPU not initialized');
|
||||
}
|
||||
|
||||
// Swap to alternate staging buffer set (double-buffering)
|
||||
const readBufferIdx = this.currentReadBufferIndex;
|
||||
this.currentReadBufferIndex = 1 - this.currentReadBufferIndex;
|
||||
|
||||
const matchCountReadBuffer = this.matchCountReadBuffers[readBufferIdx]!;
|
||||
const matchIndicesReadBuffer = this.matchIndicesReadBuffers[readBufferIdx]!;
|
||||
|
||||
// Parse ciphertext if provided
|
||||
const verifyMac = ciphertextHex && targetMacHex ? 1 : 0;
|
||||
let ciphertextWords: Uint32Array;
|
||||
let ciphertextLenBits = 0;
|
||||
let targetMac = 0;
|
||||
|
||||
if (verifyMac) {
|
||||
// Convert hex to bytes then to big-endian u32 words
|
||||
const ciphertextBytes = new Uint8Array(ciphertextHex!.length / 2);
|
||||
for (let i = 0; i < ciphertextBytes.length; i++) {
|
||||
ciphertextBytes[i] = parseInt(ciphertextHex!.substr(i * 2, 2), 16);
|
||||
}
|
||||
ciphertextLenBits = ciphertextBytes.length * 8;
|
||||
|
||||
// Pad to 4-byte boundary and convert to big-endian u32
|
||||
const paddedLen = Math.ceil(ciphertextBytes.length / 4) * 4;
|
||||
const padded = new Uint8Array(paddedLen);
|
||||
padded.set(ciphertextBytes);
|
||||
ciphertextWords = new Uint32Array(paddedLen / 4);
|
||||
for (let i = 0; i < ciphertextWords.length; i++) {
|
||||
ciphertextWords[i] =
|
||||
(padded[i * 4] << 24) |
|
||||
(padded[i * 4 + 1] << 16) |
|
||||
(padded[i * 4 + 2] << 8) |
|
||||
padded[i * 4 + 3];
|
||||
}
|
||||
|
||||
// Parse target MAC (2 bytes in high 16 bits)
|
||||
const macByte0 = parseInt(targetMacHex!.substr(0, 2), 16);
|
||||
const macByte1 = parseInt(targetMacHex!.substr(2, 2), 16);
|
||||
targetMac = (macByte0 << 24) | (macByte1 << 16);
|
||||
} else {
|
||||
ciphertextWords = new Uint32Array([0]); // Dummy
|
||||
}
|
||||
|
||||
// Resize ciphertext buffer if needed (marks bind group as dirty)
|
||||
const requiredCiphertextSize = Math.max(ciphertextWords.length * 4, 4);
|
||||
if (!this.ciphertextBuffer || this.ciphertextBufferSize < requiredCiphertextSize) {
|
||||
if (this.ciphertextBuffer) {
|
||||
this.ciphertextBuffer.destroy();
|
||||
}
|
||||
this.ciphertextBuffer = this.device.createBuffer({
|
||||
size: requiredCiphertextSize,
|
||||
usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST,
|
||||
});
|
||||
this.ciphertextBufferSize = requiredCiphertextSize;
|
||||
this.bindGroupDirty = true;
|
||||
}
|
||||
|
||||
// Write params
|
||||
const paramsData = new Uint32Array([
|
||||
targetChannelHash,
|
||||
batchOffset,
|
||||
nameLength,
|
||||
batchSize,
|
||||
targetMac,
|
||||
ciphertextWords.length,
|
||||
ciphertextLenBits,
|
||||
verifyMac,
|
||||
]);
|
||||
this.device.queue.writeBuffer(this.paramsBuffer, 0, paramsData);
|
||||
|
||||
// Write ciphertext
|
||||
this.device.queue.writeBuffer(this.ciphertextBuffer, 0, ciphertextWords as Uint32Array<ArrayBuffer>);
|
||||
|
||||
// Reset match count (reuse static zero buffer)
|
||||
this.device.queue.writeBuffer(this.matchCountBuffer, 0, GpuBruteForce.ZERO_DATA);
|
||||
|
||||
// Recreate bind group only if needed
|
||||
if (this.bindGroupDirty || !this.bindGroup) {
|
||||
this.bindGroup = this.device.createBindGroup({
|
||||
layout: this.bindGroupLayout,
|
||||
entries: [
|
||||
{ binding: 0, resource: { buffer: this.paramsBuffer } },
|
||||
{ binding: 1, resource: { buffer: this.matchCountBuffer } },
|
||||
{ binding: 2, resource: { buffer: this.matchIndicesBuffer } },
|
||||
{ binding: 3, resource: { buffer: this.ciphertextBuffer } },
|
||||
],
|
||||
});
|
||||
this.bindGroupDirty = false;
|
||||
}
|
||||
|
||||
// Create command encoder
|
||||
const commandEncoder = this.device.createCommandEncoder();
|
||||
const passEncoder = commandEncoder.beginComputePass();
|
||||
passEncoder.setPipeline(this.pipeline);
|
||||
passEncoder.setBindGroup(0, this.bindGroup);
|
||||
// Each workgroup has 256 threads, each processing 16 candidates
|
||||
const CANDIDATES_PER_THREAD = 16;
|
||||
passEncoder.dispatchWorkgroups(Math.ceil(batchSize / (256 * CANDIDATES_PER_THREAD)));
|
||||
passEncoder.end();
|
||||
|
||||
// Copy results to current staging buffers
|
||||
commandEncoder.copyBufferToBuffer(this.matchCountBuffer, 0, matchCountReadBuffer, 0, 4);
|
||||
commandEncoder.copyBufferToBuffer(
|
||||
this.matchIndicesBuffer,
|
||||
0,
|
||||
matchIndicesReadBuffer,
|
||||
0,
|
||||
1024 * 4,
|
||||
);
|
||||
|
||||
// Submit
|
||||
this.device.queue.submit([commandEncoder.finish()]);
|
||||
|
||||
// Read results from current staging buffers
|
||||
await matchCountReadBuffer.mapAsync(GPUMapMode.READ);
|
||||
const matchCount = new Uint32Array(matchCountReadBuffer.getMappedRange())[0];
|
||||
matchCountReadBuffer.unmap();
|
||||
|
||||
const matches: number[] = [];
|
||||
if (matchCount > 0) {
|
||||
await matchIndicesReadBuffer.mapAsync(GPUMapMode.READ);
|
||||
const indices = new Uint32Array(matchIndicesReadBuffer.getMappedRange());
|
||||
for (let i = 0; i < Math.min(matchCount, 1024); i++) {
|
||||
matches.push(indices[i]);
|
||||
}
|
||||
matchIndicesReadBuffer.unmap();
|
||||
}
|
||||
|
||||
return matches;
|
||||
}
|
||||
|
||||
destroy(): void {
|
||||
// Clean up persistent buffers
|
||||
this.paramsBuffer?.destroy();
|
||||
this.matchCountBuffer?.destroy();
|
||||
this.matchIndicesBuffer?.destroy();
|
||||
this.ciphertextBuffer?.destroy();
|
||||
|
||||
// Clean up double-buffered staging buffers
|
||||
this.matchCountReadBuffers[0]?.destroy();
|
||||
this.matchCountReadBuffers[1]?.destroy();
|
||||
this.matchIndicesReadBuffers[0]?.destroy();
|
||||
this.matchIndicesReadBuffers[1]?.destroy();
|
||||
|
||||
this.paramsBuffer = null;
|
||||
this.matchCountBuffer = null;
|
||||
this.matchIndicesBuffer = null;
|
||||
this.ciphertextBuffer = null;
|
||||
this.ciphertextBufferSize = 0;
|
||||
this.matchCountReadBuffers = [null, null];
|
||||
this.matchIndicesReadBuffers = [null, null];
|
||||
this.currentReadBufferIndex = 0;
|
||||
this.bindGroup = null;
|
||||
this.bindGroupDirty = true;
|
||||
|
||||
if (this.device) {
|
||||
this.device.destroy();
|
||||
this.device = null;
|
||||
}
|
||||
this.pipeline = null;
|
||||
this.bindGroupLayout = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if WebGPU is supported in the current browser.
|
||||
*/
|
||||
export function isWebGpuSupported(): boolean {
|
||||
return typeof navigator !== 'undefined' && 'gpu' in navigator;
|
||||
}
|
||||
56
frontend/lib/meshcore-cracker/src/index.ts
Normal file
56
frontend/lib/meshcore-cracker/src/index.ts
Normal file
@@ -0,0 +1,56 @@
|
||||
/**
|
||||
* MeshCore Cracker - Standalone library for cracking MeshCore GroupText packets
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* import { GroupTextCracker } from 'meshcore-cracker';
|
||||
*
|
||||
* const cracker = new GroupTextCracker();
|
||||
*
|
||||
* // Optional: load wordlist for dictionary attack
|
||||
* await cracker.loadWordlist('/words_alpha.txt');
|
||||
*
|
||||
* const result = await cracker.crack(packetHex, {
|
||||
* maxLength: 6,
|
||||
* useTimestampFilter: true,
|
||||
* useUtf8Filter: true,
|
||||
* }, (progress) => {
|
||||
* console.log(`${progress.percent.toFixed(1)}% - ETA: ${progress.etaSeconds}s`);
|
||||
* });
|
||||
*
|
||||
* if (result.found) {
|
||||
* console.log(`Room: #${result.roomName}`);
|
||||
* console.log(`Message: ${result.decryptedMessage}`);
|
||||
* }
|
||||
*
|
||||
* cracker.destroy();
|
||||
* ```
|
||||
*/
|
||||
|
||||
// Main cracker class
|
||||
export { GroupTextCracker } from './cracker';
|
||||
|
||||
// Types
|
||||
export type {
|
||||
CrackOptions,
|
||||
CrackResult,
|
||||
ProgressReport,
|
||||
ProgressCallback,
|
||||
DecodedPacket,
|
||||
} from './types';
|
||||
|
||||
// Utility exports for advanced usage
|
||||
export {
|
||||
deriveKeyFromRoomName,
|
||||
getChannelHash,
|
||||
verifyMac,
|
||||
isTimestampValid,
|
||||
isValidUtf8,
|
||||
indexToRoomName,
|
||||
roomNameToIndex,
|
||||
countNamesForLength,
|
||||
PUBLIC_ROOM_NAME,
|
||||
PUBLIC_KEY,
|
||||
} from './core';
|
||||
|
||||
export { isWebGpuSupported } from './gpu-bruteforce';
|
||||
110
frontend/lib/meshcore-cracker/src/types.ts
Normal file
110
frontend/lib/meshcore-cracker/src/types.ts
Normal file
@@ -0,0 +1,110 @@
|
||||
/**
|
||||
* Options for configuring the cracking process.
|
||||
*/
|
||||
export interface CrackOptions {
|
||||
/**
|
||||
* Maximum room name length to search (default: 8).
|
||||
* Longer names exponentially increase search time.
|
||||
*/
|
||||
maxLength?: number;
|
||||
|
||||
/**
|
||||
* Filter results by timestamp validity (default: true).
|
||||
* When enabled, rejects results where the decrypted timestamp
|
||||
* is more than 30 days old.
|
||||
*/
|
||||
useTimestampFilter?: boolean;
|
||||
|
||||
/**
|
||||
* Filter results by UTF-8 validity (default: true).
|
||||
* When enabled, rejects results containing invalid UTF-8 sequences.
|
||||
*/
|
||||
useUtf8Filter?: boolean;
|
||||
|
||||
/**
|
||||
* Resume cracking from a specific room name position.
|
||||
* Useful for resuming interrupted searches.
|
||||
*/
|
||||
startFrom?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Progress information reported during cracking.
|
||||
*/
|
||||
export interface ProgressReport {
|
||||
/** Total candidates checked so far */
|
||||
checked: number;
|
||||
|
||||
/** Total candidates to check */
|
||||
total: number;
|
||||
|
||||
/** Progress percentage (0-100) */
|
||||
percent: number;
|
||||
|
||||
/** Current cracking rate in keys/second */
|
||||
rateKeysPerSec: number;
|
||||
|
||||
/** Estimated time remaining in seconds */
|
||||
etaSeconds: number;
|
||||
|
||||
/** Time elapsed since start in seconds */
|
||||
elapsedSeconds: number;
|
||||
|
||||
/** Current room name length being tested */
|
||||
currentLength: number;
|
||||
|
||||
/** Current room name position being tested */
|
||||
currentPosition: string;
|
||||
|
||||
/** Current phase of cracking */
|
||||
phase: 'public-key' | 'wordlist' | 'bruteforce';
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback function for progress updates.
|
||||
* Called approximately 5 times per second during cracking.
|
||||
*/
|
||||
export type ProgressCallback = (report: ProgressReport) => void;
|
||||
|
||||
/**
|
||||
* Result of a cracking operation.
|
||||
*/
|
||||
export interface CrackResult {
|
||||
/** Whether a matching room name was found */
|
||||
found: boolean;
|
||||
|
||||
/** The room name (without '#' prefix) if found */
|
||||
roomName?: string;
|
||||
|
||||
/** The derived encryption key (hex) if found */
|
||||
key?: string;
|
||||
|
||||
/** The decrypted message content if found */
|
||||
decryptedMessage?: string;
|
||||
|
||||
/** Whether the operation was aborted */
|
||||
aborted?: boolean;
|
||||
|
||||
/** Position to resume from if aborted or failed */
|
||||
resumeFrom?: string;
|
||||
|
||||
/** Error message if an error occurred */
|
||||
error?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decoded packet information extracted from a MeshCore GroupText packet.
|
||||
*/
|
||||
export interface DecodedPacket {
|
||||
/** Channel hash (1 byte, hex) */
|
||||
channelHash: string;
|
||||
|
||||
/** Encrypted ciphertext (hex) */
|
||||
ciphertext: string;
|
||||
|
||||
/** MAC for verification (2 bytes, hex) */
|
||||
cipherMac: string;
|
||||
|
||||
/** Whether this is a GroupText packet */
|
||||
isGroupText: boolean;
|
||||
}
|
||||
21
frontend/lib/meshcore-cracker/tsconfig.json
Normal file
21
frontend/lib/meshcore-cracker/tsconfig.json
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2020",
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "bundler",
|
||||
"lib": ["ES2020", "DOM"],
|
||||
"types": ["@webgpu/types"],
|
||||
"declaration": true,
|
||||
"declarationMap": true,
|
||||
"sourceMap": true,
|
||||
"outDir": "./dist",
|
||||
"rootDir": "./src",
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"resolveJsonModule": true
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
||||
370105
frontend/lib/meshcore-cracker/words_alpha.txt
Normal file
370105
frontend/lib/meshcore-cracker/words_alpha.txt
Normal file
File diff suppressed because it is too large
Load Diff
608
frontend/package-lock.json
generated
608
frontend/package-lock.json
generated
@@ -17,7 +17,7 @@
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"lucide-react": "^0.562.0",
|
||||
"meshcore-cracker": "file:../references/standalone_cracker",
|
||||
"meshcore-cracker": "file:./lib/meshcore-cracker",
|
||||
"nosleep.js": "^0.12.0",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
@@ -44,6 +44,7 @@
|
||||
"../references/standalone_cracker": {
|
||||
"name": "meshcore-cracker",
|
||||
"version": "1.0.0",
|
||||
"extraneous": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@michaelhart/meshcore-decoder": "^0.2.7",
|
||||
@@ -56,6 +57,486 @@
|
||||
"typescript": "^5.7.2"
|
||||
}
|
||||
},
|
||||
"lib/meshcore-cracker": {
|
||||
"version": "1.0.0",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@michaelhart/meshcore-decoder": "^0.2.7",
|
||||
"crypto-js": "^4.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/crypto-js": "^4.2.2",
|
||||
"@webgpu/types": "^0.1.68",
|
||||
"esbuild": "^0.24.2",
|
||||
"typescript": "^5.7.2"
|
||||
}
|
||||
},
|
||||
"lib/meshcore-cracker/node_modules/@esbuild/aix-ppc64": {
|
||||
"version": "0.24.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.24.2.tgz",
|
||||
"integrity": "sha512-thpVCb/rhxE/BnMLQ7GReQLLN8q9qbHmI55F4489/ByVg2aQaQ6kbcLb6FHkocZzQhxc4gx0sCk0tJkKBFzDhA==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"aix"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"lib/meshcore-cracker/node_modules/@esbuild/android-arm": {
|
||||
"version": "0.24.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.24.2.tgz",
|
||||
"integrity": "sha512-tmwl4hJkCfNHwFB3nBa8z1Uy3ypZpxqxfTQOcHX+xRByyYgunVbZ9MzUUfb0RxaHIMnbHagwAxuTL+tnNM+1/Q==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"lib/meshcore-cracker/node_modules/@esbuild/android-arm64": {
|
||||
"version": "0.24.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.24.2.tgz",
|
||||
"integrity": "sha512-cNLgeqCqV8WxfcTIOeL4OAtSmL8JjcN6m09XIgro1Wi7cF4t/THaWEa7eL5CMoMBdjoHOTh/vwTO/o2TRXIyzg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"lib/meshcore-cracker/node_modules/@esbuild/android-x64": {
|
||||
"version": "0.24.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.24.2.tgz",
|
||||
"integrity": "sha512-B6Q0YQDqMx9D7rvIcsXfmJfvUYLoP722bgfBlO5cGvNVb5V/+Y7nhBE3mHV9OpxBf4eAS2S68KZztiPaWq4XYw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"lib/meshcore-cracker/node_modules/@esbuild/darwin-arm64": {
|
||||
"version": "0.24.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.24.2.tgz",
|
||||
"integrity": "sha512-kj3AnYWc+CekmZnS5IPu9D+HWtUI49hbnyqk0FLEJDbzCIQt7hg7ucF1SQAilhtYpIujfaHr6O0UHlzzSPdOeA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"lib/meshcore-cracker/node_modules/@esbuild/darwin-x64": {
|
||||
"version": "0.24.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.24.2.tgz",
|
||||
"integrity": "sha512-WeSrmwwHaPkNR5H3yYfowhZcbriGqooyu3zI/3GGpF8AyUdsrrP0X6KumITGA9WOyiJavnGZUwPGvxvwfWPHIA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"lib/meshcore-cracker/node_modules/@esbuild/freebsd-arm64": {
|
||||
"version": "0.24.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.24.2.tgz",
|
||||
"integrity": "sha512-UN8HXjtJ0k/Mj6a9+5u6+2eZ2ERD7Edt1Q9IZiB5UZAIdPnVKDoG7mdTVGhHJIeEml60JteamR3qhsr1r8gXvg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"freebsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"lib/meshcore-cracker/node_modules/@esbuild/freebsd-x64": {
|
||||
"version": "0.24.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.24.2.tgz",
|
||||
"integrity": "sha512-TvW7wE/89PYW+IevEJXZ5sF6gJRDY/14hyIGFXdIucxCsbRmLUcjseQu1SyTko+2idmCw94TgyaEZi9HUSOe3Q==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"freebsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"lib/meshcore-cracker/node_modules/@esbuild/linux-arm": {
|
||||
"version": "0.24.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.24.2.tgz",
|
||||
"integrity": "sha512-n0WRM/gWIdU29J57hJyUdIsk0WarGd6To0s+Y+LwvlC55wt+GT/OgkwoXCXvIue1i1sSNWblHEig00GBWiJgfA==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"lib/meshcore-cracker/node_modules/@esbuild/linux-arm64": {
|
||||
"version": "0.24.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.24.2.tgz",
|
||||
"integrity": "sha512-7HnAD6074BW43YvvUmE/35Id9/NB7BeX5EoNkK9obndmZBUk8xmJJeU7DwmUeN7tkysslb2eSl6CTrYz6oEMQg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"lib/meshcore-cracker/node_modules/@esbuild/linux-ia32": {
|
||||
"version": "0.24.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.24.2.tgz",
|
||||
"integrity": "sha512-sfv0tGPQhcZOgTKO3oBE9xpHuUqguHvSo4jl+wjnKwFpapx+vUDcawbwPNuBIAYdRAvIDBfZVvXprIj3HA+Ugw==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"lib/meshcore-cracker/node_modules/@esbuild/linux-loong64": {
|
||||
"version": "0.24.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.24.2.tgz",
|
||||
"integrity": "sha512-CN9AZr8kEndGooS35ntToZLTQLHEjtVB5n7dl8ZcTZMonJ7CCfStrYhrzF97eAecqVbVJ7APOEe18RPI4KLhwQ==",
|
||||
"cpu": [
|
||||
"loong64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"lib/meshcore-cracker/node_modules/@esbuild/linux-mips64el": {
|
||||
"version": "0.24.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.24.2.tgz",
|
||||
"integrity": "sha512-iMkk7qr/wl3exJATwkISxI7kTcmHKE+BlymIAbHO8xanq/TjHaaVThFF6ipWzPHryoFsesNQJPE/3wFJw4+huw==",
|
||||
"cpu": [
|
||||
"mips64el"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"lib/meshcore-cracker/node_modules/@esbuild/linux-ppc64": {
|
||||
"version": "0.24.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.24.2.tgz",
|
||||
"integrity": "sha512-shsVrgCZ57Vr2L8mm39kO5PPIb+843FStGt7sGGoqiiWYconSxwTiuswC1VJZLCjNiMLAMh34jg4VSEQb+iEbw==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"lib/meshcore-cracker/node_modules/@esbuild/linux-riscv64": {
|
||||
"version": "0.24.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.24.2.tgz",
|
||||
"integrity": "sha512-4eSFWnU9Hhd68fW16GD0TINewo1L6dRrB+oLNNbYyMUAeOD2yCK5KXGK1GH4qD/kT+bTEXjsyTCiJGHPZ3eM9Q==",
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"lib/meshcore-cracker/node_modules/@esbuild/linux-s390x": {
|
||||
"version": "0.24.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.24.2.tgz",
|
||||
"integrity": "sha512-S0Bh0A53b0YHL2XEXC20bHLuGMOhFDO6GN4b3YjRLK//Ep3ql3erpNcPlEFed93hsQAjAQDNsvcK+hV90FubSw==",
|
||||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"lib/meshcore-cracker/node_modules/@esbuild/linux-x64": {
|
||||
"version": "0.24.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.24.2.tgz",
|
||||
"integrity": "sha512-8Qi4nQcCTbLnK9WoMjdC9NiTG6/E38RNICU6sUNqK0QFxCYgoARqVqxdFmWkdonVsvGqWhmm7MO0jyTqLqwj0Q==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"lib/meshcore-cracker/node_modules/@esbuild/netbsd-arm64": {
|
||||
"version": "0.24.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.24.2.tgz",
|
||||
"integrity": "sha512-wuLK/VztRRpMt9zyHSazyCVdCXlpHkKm34WUyinD2lzK07FAHTq0KQvZZlXikNWkDGoT6x3TD51jKQ7gMVpopw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"netbsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"lib/meshcore-cracker/node_modules/@esbuild/netbsd-x64": {
|
||||
"version": "0.24.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.24.2.tgz",
|
||||
"integrity": "sha512-VefFaQUc4FMmJuAxmIHgUmfNiLXY438XrL4GDNV1Y1H/RW3qow68xTwjZKfj/+Plp9NANmzbH5R40Meudu8mmw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"netbsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"lib/meshcore-cracker/node_modules/@esbuild/openbsd-arm64": {
|
||||
"version": "0.24.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.24.2.tgz",
|
||||
"integrity": "sha512-YQbi46SBct6iKnszhSvdluqDmxCJA+Pu280Av9WICNwQmMxV7nLRHZfjQzwbPs3jeWnuAhE9Jy0NrnJ12Oz+0A==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"openbsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"lib/meshcore-cracker/node_modules/@esbuild/openbsd-x64": {
|
||||
"version": "0.24.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.24.2.tgz",
|
||||
"integrity": "sha512-+iDS6zpNM6EnJyWv0bMGLWSWeXGN/HTaF/LXHXHwejGsVi+ooqDfMCCTerNFxEkM3wYVcExkeGXNqshc9iMaOA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"openbsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"lib/meshcore-cracker/node_modules/@esbuild/sunos-x64": {
|
||||
"version": "0.24.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.24.2.tgz",
|
||||
"integrity": "sha512-hTdsW27jcktEvpwNHJU4ZwWFGkz2zRJUz8pvddmXPtXDzVKTTINmlmga3ZzwcuMpUvLw7JkLy9QLKyGpD2Yxig==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"sunos"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"lib/meshcore-cracker/node_modules/@esbuild/win32-arm64": {
|
||||
"version": "0.24.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.24.2.tgz",
|
||||
"integrity": "sha512-LihEQ2BBKVFLOC9ZItT9iFprsE9tqjDjnbulhHoFxYQtQfai7qfluVODIYxt1PgdoyQkz23+01rzwNwYfutxUQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"lib/meshcore-cracker/node_modules/@esbuild/win32-ia32": {
|
||||
"version": "0.24.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.24.2.tgz",
|
||||
"integrity": "sha512-q+iGUwfs8tncmFC9pcnD5IvRHAzmbwQ3GPS5/ceCyHdjXubwQWI12MKWSNSMYLJMq23/IUCvJMS76PDqXe1fxA==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"lib/meshcore-cracker/node_modules/@esbuild/win32-x64": {
|
||||
"version": "0.24.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.24.2.tgz",
|
||||
"integrity": "sha512-7VTgWzgMGvup6aSqDPLiW5zHaxYJGTO4OokMjIlrCtf+VpEL+cXKtCvg723iguPYI5oaUNdS+/V7OU2gvXVWEg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"lib/meshcore-cracker/node_modules/esbuild": {
|
||||
"version": "0.24.2",
|
||||
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.24.2.tgz",
|
||||
"integrity": "sha512-+9egpBW8I3CD5XPe0n6BfT5fxLzxrlDzqydF3aviG+9ni1lDC/OvMHcxqEFV0+LANZG5R1bFMWfUrjVsdwxJvA==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"esbuild": "bin/esbuild"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@esbuild/aix-ppc64": "0.24.2",
|
||||
"@esbuild/android-arm": "0.24.2",
|
||||
"@esbuild/android-arm64": "0.24.2",
|
||||
"@esbuild/android-x64": "0.24.2",
|
||||
"@esbuild/darwin-arm64": "0.24.2",
|
||||
"@esbuild/darwin-x64": "0.24.2",
|
||||
"@esbuild/freebsd-arm64": "0.24.2",
|
||||
"@esbuild/freebsd-x64": "0.24.2",
|
||||
"@esbuild/linux-arm": "0.24.2",
|
||||
"@esbuild/linux-arm64": "0.24.2",
|
||||
"@esbuild/linux-ia32": "0.24.2",
|
||||
"@esbuild/linux-loong64": "0.24.2",
|
||||
"@esbuild/linux-mips64el": "0.24.2",
|
||||
"@esbuild/linux-ppc64": "0.24.2",
|
||||
"@esbuild/linux-riscv64": "0.24.2",
|
||||
"@esbuild/linux-s390x": "0.24.2",
|
||||
"@esbuild/linux-x64": "0.24.2",
|
||||
"@esbuild/netbsd-arm64": "0.24.2",
|
||||
"@esbuild/netbsd-x64": "0.24.2",
|
||||
"@esbuild/openbsd-arm64": "0.24.2",
|
||||
"@esbuild/openbsd-x64": "0.24.2",
|
||||
"@esbuild/sunos-x64": "0.24.2",
|
||||
"@esbuild/win32-arm64": "0.24.2",
|
||||
"@esbuild/win32-ia32": "0.24.2",
|
||||
"@esbuild/win32-x64": "0.24.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@adobe/css-tools": {
|
||||
"version": "4.4.4",
|
||||
"resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.4.4.tgz",
|
||||
@@ -991,6 +1472,39 @@
|
||||
"@jridgewell/sourcemap-codec": "^1.4.14"
|
||||
}
|
||||
},
|
||||
"node_modules/@michaelhart/meshcore-decoder": {
|
||||
"version": "0.2.7",
|
||||
"resolved": "https://registry.npmjs.org/@michaelhart/meshcore-decoder/-/meshcore-decoder-0.2.7.tgz",
|
||||
"integrity": "sha512-a3zNbqeACibYy7XlMx6F2fMfg8FT+mP7lcYCmr8EvQk2MvBw7Qs7+bV2DAnwTf+51IRZqFEPCq55GB0scsw1WA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@noble/ed25519": "^2.3.0",
|
||||
"chalk": "^4.1.2",
|
||||
"commander": "^12.0.0",
|
||||
"crypto-js": "^4.2.0"
|
||||
},
|
||||
"bin": {
|
||||
"meshcore-decoder": "dist/cli.js"
|
||||
}
|
||||
},
|
||||
"node_modules/@michaelhart/meshcore-decoder/node_modules/commander": {
|
||||
"version": "12.1.0",
|
||||
"resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz",
|
||||
"integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@noble/ed25519": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@noble/ed25519/-/ed25519-2.3.0.tgz",
|
||||
"integrity": "sha512-M7dvXL2B92/M7dw9+gzuydL8qn/jiqNHaoR3Q+cb1q1GHV7uwE17WCyFMG+Y+TZb5izcaXk5TdJRrDUxHXL78A==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"url": "https://paulmillr.com/funding/"
|
||||
}
|
||||
},
|
||||
"node_modules/@nodelib/fs.scandir": {
|
||||
"version": "2.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
|
||||
@@ -2154,6 +2668,13 @@
|
||||
"@babel/types": "^7.28.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/crypto-js": {
|
||||
"version": "4.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/crypto-js/-/crypto-js-4.2.2.tgz",
|
||||
"integrity": "sha512-sDOLlVbHhXpAUAL0YHDUUwDZf3iN4Bwi4W6a0W0b+QcAezUbRtH4FVb+9J4h+XFPW7l/gQ9F8qC7P+Ec4k8QVQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/estree": {
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
|
||||
@@ -2306,6 +2827,13 @@
|
||||
"url": "https://opencollective.com/vitest"
|
||||
}
|
||||
},
|
||||
"node_modules/@webgpu/types": {
|
||||
"version": "0.1.68",
|
||||
"resolved": "https://registry.npmjs.org/@webgpu/types/-/types-0.1.68.tgz",
|
||||
"integrity": "sha512-3ab1B59Ojb6RwjOspYLsTpCzbNB3ZaamIAxBMmvnNkiDoLTZUOBXZ9p5nAYVEkQlDdf6qAZWi1pqj9+ypiqznA==",
|
||||
"dev": true,
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/agent-base": {
|
||||
"version": "7.1.4",
|
||||
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz",
|
||||
@@ -2581,6 +3109,37 @@
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/chalk": {
|
||||
"version": "4.1.2",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
|
||||
"integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ansi-styles": "^4.1.0",
|
||||
"supports-color": "^7.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/chalk?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/chalk/node_modules/ansi-styles": {
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
|
||||
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"color-convert": "^2.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/check-error": {
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.3.tgz",
|
||||
@@ -2648,6 +3207,24 @@
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/color-convert": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
||||
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"color-name": "~1.1.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=7.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/color-name": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
|
||||
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/combined-stream": {
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
|
||||
@@ -2677,6 +3254,12 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/crypto-js": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.2.0.tgz",
|
||||
"integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/css.escape": {
|
||||
"version": "1.5.1",
|
||||
"resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz",
|
||||
@@ -3168,6 +3751,15 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/has-flag": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
|
||||
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/has-symbols": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
|
||||
@@ -3516,7 +4108,7 @@
|
||||
}
|
||||
},
|
||||
"node_modules/meshcore-cracker": {
|
||||
"resolved": "../references/standalone_cracker",
|
||||
"resolved": "lib/meshcore-cracker",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/micromatch": {
|
||||
@@ -4288,6 +4880,18 @@
|
||||
"node": ">=16 || 14 >=14.17"
|
||||
}
|
||||
},
|
||||
"node_modules/supports-color": {
|
||||
"version": "7.2.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
|
||||
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"has-flag": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/supports-preserve-symlinks-flag": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"lucide-react": "^0.562.0",
|
||||
"meshcore-cracker": "file:../references/standalone_cracker",
|
||||
"meshcore-cracker": "file:./lib/meshcore-cracker",
|
||||
"nosleep.js": "^0.12.0",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
|
||||
Reference in New Issue
Block a user