mojic 1.0.2 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CONTRIBUTING.md CHANGED
@@ -1,23 +1,56 @@
1
1
  # Contributing to Mojic
2
2
 
3
- First off, thanks for taking the time to contribute! 🎉
3
+ First off, thanks for taking the time to contribute!
4
4
 
5
5
  The following is a set of guidelines for contributing to Mojic. These are mostly guidelines, not rules. Use your best judgment, and feel free to propose changes to this document in a pull request.
6
6
 
7
- ## How Can I Contribute?
7
+ ## Development Workflow
8
8
 
9
- ### Reporting Bugs
9
+ We use the standard GitHub Pull Request workflow.
10
+
11
+ 1. **Fork** the repository on GitHub:
12
+ [https://github.com/notamitgamer/mojic](https://github.com/notamitgamer/mojic)
13
+
14
+ 2. **Clone** your fork locally:
15
+ ```bash
16
+ git clone [https://github.com/YOUR_USERNAME/mojic.git](https://github.com/YOUR_USERNAME/mojic.git)
17
+ cd mojic
18
+ ```
19
+
20
+ 3. **Create a Branch** for your feature or bugfix:
21
+ ```bash
22
+ git checkout -b feature/amazing-feature
23
+ ```
24
+
25
+ 4. **Install Dependencies**:
26
+ ```bash
27
+ npm install
28
+ ```
29
+
30
+ 5. **Test your changes**:
31
+ You can run the CLI locally using `npm start` or linking the package.
32
+ ```bash
33
+ # Run directly
34
+ npm start -- encode test.c
35
+ ```
36
+
37
+ 6. **Commit** your changes (see the style guide below).
10
38
 
11
- This section guides you through submitting a bug report for Mojic. Following these guidelines helps maintainers and the community understand your report, reproduce the behavior, and find related reports.
39
+ 7. **Push** to your fork:
40
+ ```bash
41
+ git push origin feature/amazing-feature
42
+ ```
12
43
 
44
+ 8. **Open a Pull Request** on the main repository (`notamitgamer/mojic`).
45
+
46
+ ## How Can I Contribute?
47
+
48
+ ### Reporting Bugs
13
49
  * **Use a clear and descriptive title** for the issue to identify the problem.
14
50
  * **Describe the exact steps which reproduce the problem** in as many details as possible.
15
51
  * **Provide specific examples** to demonstrate the steps.
16
52
 
17
53
  ### Suggesting Enhancements
18
-
19
- This section guides you through submitting an enhancement suggestion for Mojic, including completely new features and minor improvements to existing functionality.
20
-
21
54
  * **Use a clear and descriptive title** for the issue to identify the suggestion.
22
55
  * **Provide a step-by-step description of the suggested enhancement** in as many details as possible.
23
56
  * **Explain why this enhancement would be useful** to most Mojic users.
@@ -25,14 +58,13 @@ This section guides you through submitting an enhancement suggestion for Mojic,
25
58
  ## Styleguides
26
59
 
27
60
  ### Git Commit Messages
28
-
29
61
  * Use the present tense ("Add feature" not "Added feature")
30
62
  * Use the imperative mood ("Move cursor to..." not "Moves cursor to...")
31
63
  * Limit the first line to 72 characters or less
32
64
 
33
65
  ### Mojic Code Style
34
-
35
- * **Cipher Logic:** Changes to `CipherEngine.js` must ensure backward compatibility with the header format.
66
+ * **Cipher Logic:** Changes to `CipherEngine.js` must ensure backward compatibility with the header format if possible.
36
67
  * **Streams:** Always use `StringDecoder` when handling text streams to prevent multi-byte emoji corruption.
68
+ * **Linting:** Ensure your code is clean and readable.
37
69
 
38
- Happy Hacking! 🚀
70
+ Happy Hacking!
package/README.md CHANGED
@@ -1,27 +1,36 @@
1
- # Mojic 🔒🔮
1
+ # Mojic v1.1.0
2
2
 
3
- > **Obfuscate C source code into a randomized stream of emojis using password-seeded encryption.**
3
+ > **Operation Polymorphic Chaos: Obfuscate C source code into a randomized, password-seeded stream of emojis.**
4
4
 
5
- **Mojic** (Magic + Emoji + Logic) is a CLI tool that transforms readable C code into an unreadable mess of emojis. Unlike simple substitution ciphers, Mojic uses your password to seed a Pseudo-Random Number Generator (PRNG), creating a unique "Emoji Universe" for every password.
5
+ **Mojic** (Magic + Emoji + Logic) is a sophisticated CLI tool designed to transform readable C code into an unrecognizable chaotic stream of emojis. Unlike simple substitution ciphers, Mojic uses your password to seed a cryptographically strong Pseudo-Random Number Generator (PRNG), creating a unique "Emoji Universe" and rolling cipher for every single session.
6
6
 
7
- ## 🚀 Features
7
+ ## Key Features
8
8
 
9
- * **🔐 Seeded Shuffling:** Your password derives a seed that shuffles the emoji mapping. Two different passwords will produce completely different emoji outputs for the same file.
10
- * **🌚 Moon Header Protocol:** The file header (Salt + Auth Hash) is encoded using only Moon and Clock emojis to allow metadata reading before decryption.
11
- * **📂 Recursive Processing:** Encrypt or decrypt entire project directories with one flag.
12
- * **🔄 Security Rotation:** Rotate passwords or re-scramble the entropy of encrypted files without seeing the plaintext.
13
- * **⚡ Stream Based:** Handles large files efficiently using Node.js streams.
9
+ * ** Xoshiro256** PRNG:** Uses a high-quality 256-bit state PRNG (seeded via PBKDF2-SHA512) to handle shuffling and polymorphism.
10
+ * ** Polymorphic Keywords:** Common C keywords (`int`, `void`, `return`) are mapped to emojis that *change* every time they appear based on the PRNG state. Frequency analysis is impossible.
11
+ * ** Base-1024 Compression:** Non-keyword code is compressed using a custom Base-1024 scheme (5 bytes → 4 emojis), keeping file size manageable.
12
+ * ** Integrity Sealed:** Every file ends with an HMAC-SHA256 signature. Any tampering with the emoji stream results in an immediate `FILE_TAMPERED` error.
13
+ * ** Moon Header Protocol:** Metadata (Salt + Auth Check) is encoded using a specific alphabet of Moon and Clock phases (`🌑🌒🕐`), allowing instant password verification before decryption starts.
14
+ * ** Stream Architecture:** Built on Node.js `Transform` streams to handle large files efficiently with minimal memory footprint.
14
15
 
15
- ## 📦 Installation
16
+ ## Installation
17
+
18
+ Since Mojic is available on npm, you can install it globally with a single command:
16
19
 
17
20
  ```bash
18
21
  npm install -g mojic
19
22
  ```
20
23
 
21
- ## 🛠️ Usage
24
+ Or run it directly using `npx` without installing:
25
+
26
+ ```bash
27
+ npx mojic encode main.c
28
+ ```
29
+
30
+ ## Usage
22
31
 
23
32
  ### 1. Encrypting Code (`encode`)
24
- Turns a `.c` file into a `.mojic` file.
33
+ Transforms a `.c` file into a `.mojic` file.
25
34
 
26
35
  ```bash
27
36
  # Encrypt a single file
@@ -29,6 +38,9 @@ mojic encode main.c
29
38
 
30
39
  # Encrypt an entire directory recursively
31
40
  mojic encode ./src -r
41
+
42
+ # Flatten/Minify code structure before encryption (Removes newlines/indentation)
43
+ mojic encode main.c --flat
32
44
  ```
33
45
  *You will be prompted to create a password. This password is required to decrypt.*
34
46
 
@@ -44,24 +56,42 @@ mojic decode ./src -r
44
56
  ```
45
57
 
46
58
  ### 3. Security & Rotation Tools (`srt`)
47
- Manage encrypted files without revealing their contents.
59
+ Manage encrypted files without ever revealing their plaintext contents.
48
60
 
49
61
  ```bash
50
- # Change the password of an encrypted file
62
+ # Rotate Password: Changes the password of an encrypted file
51
63
  mojic srt --pass secret.mojic
52
64
 
53
- # Re-shuffle the encryption (New Salt) with the SAME password
54
- # (Useful if you want to change the visual emoji pattern without changing the password)
65
+ # Re-Encrypt: Re-shuffles the entropy (New Salt) with the SAME password
66
+ # (Useful to change the visual emoji pattern without changing the password)
55
67
  mojic srt --re secret.mojic
56
68
  ```
57
69
 
58
- ## 🧠 How it Works
70
+ ## Under the Hood (Algorithm)
71
+
72
+ Mojic v1.1.0 implements a custom crypto-system dubbed **"Operation Polymorphic Chaos"**.
73
+
74
+ 1. **Derivation Phase:**
75
+ * **Input:** User Password + 16-byte Random Salt.
76
+ * **KDF:** `PBKDF2-SHA512` (100,000 iterations).
77
+ * **Output:** 64 bytes (32 bytes for PRNG Seed, 32 bytes for HMAC Auth Key).
78
+
79
+ 2. **The Emoji Universe:**
80
+ * The engine generates a universe of ~1,100 valid emojis (Emoticons, Transport, Symbols).
81
+ * This universe is **shuffled** using the `Xoshiro256**` PRNG initialized with the derived seed.
82
+
83
+ 3. **Polymorphic Encryption:**
84
+ * **C Keywords:** The engine detects C keywords (e.g., `while`). It assigns them a "Base Emoji" from the shuffled universe.
85
+ * **The Twist:** It doesn't just print the Base Emoji. It calculates a random offset using the PRNG to pick a *different* emoji that maps back to the keyword. This means `int` might look like `🚀` on line 1 and `🌮` on line 5.
86
+
87
+ 4. **Base-1024 Encoding:**
88
+ * Non-keyword data is buffered into 5-byte chunks.
89
+ * These chunks are treated as a single large integer and converted into 4 base-1024 digits (mapped to emojis), effectively compressing the stream density.
59
90
 
60
- 1. **Key Derivation:** Mojic uses **PBKDF2** to derive a 32-bit seed and an Auth Hash from your password and a random Salt.
61
- 2. **The Shuffle:** It uses a custom **Mulberry32 PRNG** seeded with your key to shuffle a "Universe" of ~1500 emojis.
62
- 3. **Tokenization:** C keywords (`int`, `return`, `void`) are tokenized and mapped to unique emojis from the shuffled universe. Remaining characters map to other emojis.
63
- 4. **The Header:** The Salt and Auth Hash are written to the top of the file using a fixed "Header Alphabet" (🌑🌒🌓🌔...) so the tool can verify your password before attempting decryption.
91
+ 5. **The Header:**
92
+ * The Salt and a 4-byte Auth Check are written to the file header using the **Moon/Clock Alphabet** (`🌑🌒🌓🌔...`).
93
+ * **Benefit:** This allows `mojic` to tell you "Incorrect Password" instantly, rather than churning out garbage data first.
64
94
 
65
- ## 📄 License
95
+ ## License
66
96
 
67
- This project is licensed under the Apache License 2.0 - see the [LICENSE](LICENSE) file for details.
97
+ This project is licensed under the Apache License 2.0.
package/SECURITY.md CHANGED
@@ -4,10 +4,10 @@
4
4
 
5
5
  We take security seriously.
6
6
 
7
- If you discover a security vulnerability within Mojic (e.g., in the `CipherEngine` logic or the PRNG implementation), please send an e-mail to **amitdutta4255@gmail.com**. All security vulnerabilities will be promptly addressed.
7
+ If you discover a security vulnerability within Mojic (e.g., in the `CipherEngine` logic or the PRNG implementation), please send an e-mail to **amitdutta4255@gmail.com** or **mail@amit.is-a.dev**. All security vulnerabilities will be promptly addressed.
8
8
 
9
9
  **Please do not open public issues for security vulnerabilities.**
10
10
 
11
11
  ### Scope
12
- * **Supported:** Issues regarding key derivation, salt collisions, or stream buffer overflows.
12
+ * **Supported:** Issues regarding key derivation, salt collisions, HMAC integrity failures, or stream buffer overflows.
13
13
  * **Not Supported:** Brute-forcing weak passwords (users are responsible for password strength).
package/bin/mojic.js CHANGED
@@ -12,7 +12,7 @@ import { CipherEngine } from '../lib/CipherEngine.js';
12
12
  program
13
13
  .name('mojic')
14
14
  .description('Obfuscate C source code into emojis')
15
- .version('1.0.2')
15
+ .version('1.1.0')
16
16
  .addHelpCommand('help [command]', 'Display help for command')
17
17
  .showHelpAfterError();
18
18
 
@@ -78,6 +78,19 @@ const createHeaderSkipper = () => {
78
78
  });
79
79
  };
80
80
 
81
+ // --- FLATTENING (Minifier) ---
82
+ const createMinifier = () => {
83
+ return new Transform({
84
+ transform(chunk, encoding, cb) {
85
+ let str = chunk.toString();
86
+ str = str.replace(/\r?\n|\r/g, ' ');
87
+ str = str.replace(/\s+/g, ' ');
88
+ this.push(str);
89
+ cb();
90
+ }
91
+ });
92
+ };
93
+
81
94
  // --- Recursive Logic ---
82
95
 
83
96
  async function traverseDirectory(currentPath, extension, callback) {
@@ -99,18 +112,20 @@ program
99
112
  .command('encode')
100
113
  .argument('<path>', 'File or Directory to encode')
101
114
  .option('-r, --recursive', 'Recursively encrypt all .c files in directory')
115
+ .option('-f, --flat', 'Flatten structure (strip whitespace/newlines) before encrypting')
102
116
  .description('Encrypt a file or directory into emojis')
103
117
  .action(async (targetPath, options) => {
104
118
  try {
105
119
  if (!fs.existsSync(targetPath)) throw new Error('Path not found');
106
120
  const stats = fs.statSync(targetPath);
107
121
 
108
- // Validation
109
122
  if (stats.isDirectory() && !options.recursive) {
110
123
  throw new Error(`'${targetPath}' is a directory. Use -r to process recursively.`);
111
124
  }
112
125
 
113
- console.log(chalk.blue('🔒 Initiating Mojic Encryption...'));
126
+ console.log(chalk.blue('Initiating Mojic Encryption v1.1...'));
127
+ if (options.flat) console.log(chalk.yellow(' -> Structural Flattening Enabled'));
128
+
114
129
  const password = await promptPassword('Create password for file(s):');
115
130
 
116
131
  const processFile = async (filePath) => {
@@ -118,7 +133,7 @@ program
118
133
  console.log(chalk.dim(` Processing: ${path.basename(filePath)} -> ${path.basename(outputName)}`));
119
134
 
120
135
  const engine = new CipherEngine(password);
121
- await engine.init(); // Unique salt per file
136
+ await engine.init();
122
137
 
123
138
  const readStream = fs.createReadStream(filePath);
124
139
  const writeStream = fs.createWriteStream(outputName);
@@ -126,7 +141,13 @@ program
126
141
  writeStream.write(engine._encodeHeader());
127
142
 
128
143
  await new Promise((resolve, reject) => {
129
- readStream
144
+ let pipeline = readStream;
145
+
146
+ if (options.flat) {
147
+ pipeline = pipeline.pipe(createMinifier());
148
+ }
149
+
150
+ pipeline
130
151
  .pipe(engine.getEncryptStream())
131
152
  .pipe(writeStream)
132
153
  .on('finish', resolve)
@@ -135,12 +156,12 @@ program
135
156
  };
136
157
 
137
158
  if (stats.isDirectory()) {
138
- console.log(chalk.blue(`📂 Scanning directory: ${targetPath}`));
159
+ console.log(chalk.blue(`Scanning directory: ${targetPath}`));
139
160
  await traverseDirectory(targetPath, '.c', processFile);
140
- console.log(chalk.green('Batch encryption complete.'));
161
+ console.log(chalk.green('Batch encryption complete.'));
141
162
  } else {
142
163
  await processFile(targetPath);
143
- console.log(chalk.green(`✅ Encrypted.`));
164
+ console.log(chalk.green(`Encrypted.`));
144
165
  }
145
166
 
146
167
  } catch (err) {
@@ -162,8 +183,8 @@ program
162
183
  throw new Error(`'${targetPath}' is a directory. Use -r to process recursively.`);
163
184
  }
164
185
 
165
- console.log(chalk.blue('🔓 Initiating Decryption...'));
166
- const password = await promptPassword('Enter password:'); // Assume same pass for dir
186
+ console.log(chalk.blue('Initiating Decryption...'));
187
+ const password = await promptPassword('Enter password:');
167
188
 
168
189
  const processFile = async (filePath) => {
169
190
  try {
@@ -174,36 +195,61 @@ program
174
195
  const metadata = CipherEngine.decodeHeader(headerStr);
175
196
 
176
197
  const engine = new CipherEngine(password);
177
- await engine.init(metadata.saltHex);
178
-
179
- if (engine.authHash.toString('hex') !== metadata.authHex) {
180
- console.log(chalk.red(` ❌ Skipped ${path.basename(filePath)}: Incorrect Password`));
181
- return;
182
- }
198
+
199
+ // Pass the Auth Check Hex (if available in new format)
200
+ await engine.init(metadata.saltHex, metadata.authCheckHex);
183
201
 
184
202
  const readStream = fs.createReadStream(filePath);
185
203
  const writeStream = fs.createWriteStream(outputName);
186
204
 
187
205
  await new Promise((resolve, reject) => {
206
+ const decryptStream = engine.getDecryptStream();
207
+
208
+ decryptStream.on('error', (err) => {
209
+ reject(err);
210
+ });
211
+
188
212
  readStream
189
213
  .pipe(createHeaderSkipper())
190
- .pipe(engine.getDecryptStream())
214
+ .pipe(decryptStream)
191
215
  .pipe(writeStream)
192
216
  .on('finish', resolve)
193
217
  .on('error', reject);
194
218
  });
219
+
220
+ return true; // Success
195
221
  } catch (e) {
196
- console.log(chalk.red(` ⚠️ Error processing ${path.basename(filePath)}: ${e.message}`));
222
+ const outputName = filePath.replace(/\.mojic$/, '') + '.restored.c';
223
+
224
+ // Cleanup output file on error
225
+ setTimeout(() => {
226
+ if (fs.existsSync(outputName)) {
227
+ try { fs.unlinkSync(outputName); } catch(ign){}
228
+ }
229
+ }, 100);
230
+
231
+ // Specific Error Messaging (No Emojis)
232
+ if (e.message === "WRONG_PASSWORD") {
233
+ console.log(chalk.red(` Error: Incorrect Password`));
234
+ } else if (e.message === "FILE_TAMPERED") {
235
+ console.log(chalk.red(` Error: File Tampered! Integrity check failed.`));
236
+ } else {
237
+ console.log(chalk.red(` Error: ${e.message}`));
238
+ }
239
+
240
+ return false; // Failure
197
241
  }
198
242
  };
199
243
 
200
244
  if (stats.isDirectory()) {
201
- console.log(chalk.blue(`📂 Scanning directory: ${targetPath}`));
245
+ console.log(chalk.blue(`Scanning directory: ${targetPath}`));
202
246
  await traverseDirectory(targetPath, '.mojic', processFile);
203
- console.log(chalk.green('Batch decryption complete.'));
247
+ console.log(chalk.green('Batch decryption complete.'));
204
248
  } else {
205
- await processFile(targetPath);
206
- console.log(chalk.green(`✅ Restored.`));
249
+ const success = await processFile(targetPath);
250
+ if (success) {
251
+ console.log(chalk.green(`Restored.`));
252
+ }
207
253
  }
208
254
 
209
255
  } catch (err) {
@@ -211,32 +257,24 @@ program
211
257
  }
212
258
  });
213
259
 
214
- // --- Security Rotation Tools (SRT) ---
260
+ // --- SRT ---
215
261
 
216
262
  const rotatePassword = async (file) => {
217
263
  try {
218
264
  if (!fs.existsSync(file)) throw new Error('File not found');
219
- console.log(chalk.yellow(`🔄 Rotating Password for ${path.basename(file)}...`));
265
+ console.log(chalk.yellow(`Rotating Password for ${path.basename(file)}...`));
220
266
 
221
- // 1. Authenticate Old
222
267
  const headerStr = await getStreamHeader(file);
223
268
  const metadata = CipherEngine.decodeHeader(headerStr);
224
269
  const oldPass = await promptPassword('Enter CURRENT password:');
225
270
 
226
271
  const oldEngine = new CipherEngine(oldPass);
227
- await oldEngine.init(metadata.saltHex);
272
+ await oldEngine.init(metadata.saltHex, metadata.authCheckHex);
228
273
 
229
- if (oldEngine.authHash.toString('hex') !== metadata.authHex) {
230
- console.error(chalk.red('❌ Incorrect Current Password'));
231
- process.exit(1);
232
- }
233
-
234
- // 2. Init New
235
274
  const newPass = await promptPassword('Enter NEW password:');
236
275
  const newEngine = new CipherEngine(newPass);
237
276
  await newEngine.init();
238
277
 
239
- // 3. Process
240
278
  const tempFile = file + '.tmp';
241
279
  const readStream = fs.createReadStream(file);
242
280
  const writeStream = fs.createWriteStream(tempFile);
@@ -244,9 +282,13 @@ const rotatePassword = async (file) => {
244
282
  writeStream.write(newEngine._encodeHeader());
245
283
 
246
284
  await new Promise((resolve, reject) => {
285
+ const decryptStream = oldEngine.getDecryptStream();
286
+
287
+ decryptStream.on('error', (err) => reject(err));
288
+
247
289
  readStream
248
290
  .pipe(createHeaderSkipper())
249
- .pipe(oldEngine.getDecryptStream())
291
+ .pipe(decryptStream)
250
292
  .pipe(newEngine.getEncryptStream())
251
293
  .pipe(writeStream)
252
294
  .on('finish', resolve)
@@ -254,36 +296,33 @@ const rotatePassword = async (file) => {
254
296
  });
255
297
 
256
298
  fs.renameSync(tempFile, file);
257
- console.log(chalk.green(`✅ Password updated.`));
299
+ console.log(chalk.green(`Password updated.`));
258
300
 
259
301
  } catch (err) {
260
- console.error(chalk.red('Error:'), err.message);
302
+ if (fs.existsSync(file + '.tmp')) fs.unlinkSync(file + '.tmp');
303
+ if (err.message === "WRONG_PASSWORD") {
304
+ console.error(chalk.red('Error: Incorrect Current Password'));
305
+ } else {
306
+ console.error(chalk.red('Error:'), err.message);
307
+ }
261
308
  }
262
309
  };
263
310
 
264
311
  const reEncrypt = async (file) => {
265
312
  try {
266
313
  if (!fs.existsSync(file)) throw new Error('File not found');
267
- console.log(chalk.yellow(`🎲 Re-shuffling Entropy for ${path.basename(file)}...`));
314
+ console.log(chalk.yellow(`Re-shuffling Entropy for ${path.basename(file)}...`));
268
315
 
269
- // 1. Authenticate
270
316
  const headerStr = await getStreamHeader(file);
271
317
  const metadata = CipherEngine.decodeHeader(headerStr);
272
318
  const password = await promptPassword('Enter password:');
273
319
 
274
320
  const oldEngine = new CipherEngine(password);
275
- await oldEngine.init(metadata.saltHex);
321
+ await oldEngine.init(metadata.saltHex, metadata.authCheckHex);
276
322
 
277
- if (oldEngine.authHash.toString('hex') !== metadata.authHex) {
278
- console.error(chalk.red('❌ Incorrect Password'));
279
- process.exit(1);
280
- }
281
-
282
- // 2. Init New
283
323
  const newEngine = new CipherEngine(password);
284
324
  await newEngine.init();
285
325
 
286
- // 3. Process
287
326
  const tempFile = file + '.tmp';
288
327
  const readStream = fs.createReadStream(file);
289
328
  const writeStream = fs.createWriteStream(tempFile);
@@ -291,9 +330,12 @@ const reEncrypt = async (file) => {
291
330
  writeStream.write(newEngine._encodeHeader());
292
331
 
293
332
  await new Promise((resolve, reject) => {
333
+ const decryptStream = oldEngine.getDecryptStream();
334
+ decryptStream.on('error', (err) => reject(err));
335
+
294
336
  readStream
295
337
  .pipe(createHeaderSkipper())
296
- .pipe(oldEngine.getDecryptStream())
338
+ .pipe(decryptStream)
297
339
  .pipe(newEngine.getEncryptStream())
298
340
  .pipe(writeStream)
299
341
  .on('finish', resolve)
@@ -301,10 +343,15 @@ const reEncrypt = async (file) => {
301
343
  });
302
344
 
303
345
  fs.renameSync(tempFile, file);
304
- console.log(chalk.green(`✅ File re-encrypted.`));
346
+ console.log(chalk.green(`File re-encrypted.`));
305
347
 
306
348
  } catch (err) {
307
- console.error(chalk.red('Error:'), err.message);
349
+ if (fs.existsSync(file + '.tmp')) fs.unlinkSync(file + '.tmp');
350
+ if (err.message === "WRONG_PASSWORD") {
351
+ console.error(chalk.red('Error: Incorrect Password'));
352
+ } else {
353
+ console.error(chalk.red('Error:'), err.message);
354
+ }
308
355
  }
309
356
  };
310
357
 
@@ -313,11 +360,6 @@ program
313
360
  .description('Security and Rotation Tools')
314
361
  .option('--pass <file>', 'Update the password for an existing file')
315
362
  .option('--re <file>', 'Re-encrypt with new random seed (Same password)')
316
- .addHelpText('after', `
317
- Examples:
318
- $ mojic srt --pass secret.mojic # Change the password of an encrypted file
319
- $ mojic srt --re secret.mojic # Re-scramble the emojis (new salt) with same password
320
- `)
321
363
  .action(async (options) => {
322
364
  if (options.pass) {
323
365
  await rotatePassword(options.pass);
@@ -329,12 +371,4 @@ Examples:
329
371
  }
330
372
  });
331
373
 
332
- program.addHelpText('after', `
333
- Usage Examples:
334
- $ mojic encode test.c # Encrypt a single C file
335
- $ mojic encode ./src -r # Recursively encrypt all .c files in ./src
336
- $ mojic decode test.mojic # Decrypt a single file
337
- $ mojic decode ./src -r # Recursively decrypt all .mojic files
338
- `);
339
-
340
374
  program.parse(process.argv);
@@ -3,109 +3,154 @@ import { Transform } from 'stream';
3
3
  import { StringDecoder } from 'string_decoder';
4
4
 
5
5
  /**
6
- * Emojic CipherEngine
7
- * Handles the logic for mapping C-code to Emojis based on a password seed.
8
- * * * Updates:
9
- * - Uses Intl.Segmenter for robust Emoji/Grapheme decoding.
10
- * - Implements 'tail buffering' in streams to prevent splitting multi-codepoint emojis.
6
+ * MOJIC v1.1.0 CIPHER ENGINE
7
+ * "Operation Polymorphic Chaos"
8
+ * * Features:
9
+ * - Xoshiro256** PRNG (256-bit State)
10
+ * - Base-1024 Compression (5 bytes -> 4 emojis)
11
+ * - Polymorphic Keyword Encryption (Rolling Cipher)
12
+ * - HMAC-SHA256 Integrity Footer
13
+ * - Line Wrapping (New: Prevents lag in editors)
14
+ * - Instant Password Verification (New: Header Auth Check)
11
15
  */
12
16
 
17
+ // --- EMOJI UNIVERSE GENERATION ---
13
18
  const HEADER_ALPHABET = ['🌑', '🌒', '🌓', '🌔', '🌕', '🌖', '🌗', '🌘', '🕐', '🕑', '🕒', '🕓', '🕔', '🕕', '🕖', '🕗'];
14
19
 
15
- const EMOJI_UNIVERSE = [
16
- '😀','😃','😄','😁','😆','😅','🤣','😂','🙂','🙃','😉','😊','😇','🥰','😍','🤩',
17
- '😘','😗','☺️','😚','😙','🥲','😋','😛','😜','🤪','😝','🤑','🤗','🤭','🤫','🤔',
18
- '🤐','🤨','😐','😑','😶','😏','😒','🙄','😬','🤥','😌','😔','😪','🤤','😴','😷',
19
- '🤒','🤕','🤢','🤮','🤧','🥵','🥶','🥴','😵','🤯','🤠','🥳','😎','🤓','🧐','😕',
20
- '😟','🙁','☹️','😮','😯','😲','😳','🥺','😦','😧','😨','😰','😥','😢','😭','😱',
21
- '😖','😣','😞','😓','😩','😫','🥱','😤','😡','😠','🤬','😈','👿','💀','☠️','💩',
22
- '🤡','👹','👺','👻','👽','👾','🤖','😺','😸','😹','😻','😼','😽','🙀','😿','😾',
23
- '🙈','🙉','🙊','💋','💌','💘','💝','💖','💗','💓','💞','💕','💟','❣️','💔','❤️',
24
- '🧡','💛','💚','💙','💜','🤎','🖤','🤍','💯','💢','💥','💫','💦','💨','🕳️','💣',
25
- '💬','👁️','💭','💤','👋','🤚','🖐️','✋','🖖','👌','🤏','✌️','🤞','🤟','🤘',
26
- '🤙','👈','👉','👆','🖕','👇','☝️','👍','👎','✊','👊','🤛','🤜','👏','🙌','👐',
27
- '🤲','🤝','🙏','✍️','💅','🤳','💪','🦾','🦿','🦵','🦶','👂','🦻','👃','🧠','🫀',
28
- '🫁','🦷','🦴','👀','👅','👄','👶','🧒','👦','👧','🧑','👱','👨','🧔','👨‍🦰','👨‍🦱',
29
- '🔥','🌈','☀️','⛈️','🌩️','❄️','🌵','🌷','🌲','🌳','🌴','🐲','🐉','🦕','🦖','🐍',
30
- '🐎','🦄','🦓','🐆','🐅','🐂','🐄','🐖','🐏','🐑','🐐','🐪','🐫','🦙','🦒','🐘',
31
- '🦏','🦛','🐁','🐀','🐹','🐰','🐇','🐿️','🦔','🦇','🐻','🐨','🐼','🦥','🦦','🦨',
32
- '🦘','🦡','🐾','🦃','🐔','🐓','🐣','🐤','🐥','🐦','🐧','🕊️','🦅','🦆','🦢','🦉',
33
- '🎈','🧨','🧧','🎀','🎁','🎗️','🎟️','🎫','🎖️','🏆','🏅','🥇','🥈','🥉','⚽','⚾',
34
- '🥎','🏀','🏐','🏈','🏉','🎾','🥏','🎳','🏏','🏑','🏒','🥍','🏓','🏸','🥊','🥋',
35
- '🥅','⛳','⛸️','🎣','🤿','🎽','🎿','🛷','🥌','🎯','🪀','🪁','🎱','🔮','🧿','🎮'
36
- ];
20
+ const generateUniverse = () => {
21
+ const universe = [];
22
+ const ranges = [
23
+ [0x1F600, 0x1F64F], // Emoticons
24
+ [0x1F300, 0x1F5FF], // Misc Symbols
25
+ [0x1F680, 0x1F6FF], // Transport
26
+ [0x1F900, 0x1F9FF] // Supplemental
27
+ ];
28
+
29
+ const headerSet = new Set(HEADER_ALPHABET);
30
+
31
+ for (const [start, end] of ranges) {
32
+ for (let code = start; code <= end; code++) {
33
+ const char = String.fromCodePoint(code);
34
+ if (!headerSet.has(char)) {
35
+ universe.push(char);
36
+ }
37
+ }
38
+ }
39
+ return universe; // Expect > 1100 chars
40
+ };
41
+
42
+ const RAW_UNIVERSE = generateUniverse();
43
+
44
+ if (RAW_UNIVERSE.length < 1080) {
45
+ throw new Error("Critical: Emoji Universe generation failed to produce enough tokens.");
46
+ }
37
47
 
38
48
  const C_KEYWORDS = [
39
49
  'auto', 'break', 'case', 'char', 'const', 'continue', 'default', 'do',
40
50
  'double', 'else', 'enum', 'extern', 'float', 'for', 'goto', 'if',
41
51
  'int', 'long', 'register', 'return', 'short', 'signed', 'sizeof', 'static',
42
52
  'struct', 'switch', 'typedef', 'union', 'unsigned', 'void', 'volatile', 'while',
43
- 'include', 'define', 'main', 'printf', 'NULL'
53
+ 'include', 'define', 'main', 'printf', 'NULL', '#include', '#define'
44
54
  ];
45
55
 
46
- const ASCII_CHARS = Array.from({ length: 95 }, (_, i) => String.fromCharCode(i + 32))
47
- .concat(['\n', '\t', '\r']);
56
+ // --- PRNG: Xoshiro256** ---
57
+ class Xoshiro256 {
58
+ constructor(seedBuffer) {
59
+ if (seedBuffer.length < 32) throw new Error("Seed too short");
60
+ this.s = [
61
+ seedBuffer.readBigUInt64BE(0),
62
+ seedBuffer.readBigUInt64BE(8),
63
+ seedBuffer.readBigUInt64BE(16),
64
+ seedBuffer.readBigUInt64BE(24)
65
+ ];
66
+ }
67
+
68
+ next() {
69
+ const result = this.rotl(this.s[1] * 5n, 7n) * 9n;
70
+ const t = this.s[1] << 17n;
71
+
72
+ this.s[2] ^= this.s[0];
73
+ this.s[3] ^= this.s[1];
74
+ this.s[1] ^= this.s[2];
75
+ this.s[0] ^= this.s[3];
48
76
 
49
- const TOTAL_TOKENS = C_KEYWORDS.length + ASCII_CHARS.length;
77
+ this.s[2] ^= t;
78
+ this.s[3] = this.rotl(this.s[3], 45n);
79
+
80
+ return result;
81
+ }
82
+
83
+ nextFloat() {
84
+ const val = Number(this.next() >> 11n);
85
+ return val * (2 ** -53);
86
+ }
87
+
88
+ rotl(x, k) {
89
+ return (x << k) | (x >> (64n - k));
90
+ }
91
+ }
50
92
 
51
93
  export class CipherEngine {
52
94
  constructor(password) {
53
95
  this.password = password;
54
- this.tokenMap = new Map();
55
- this.reverseMap = new Map();
96
+ this.keywordMap = new Map();
97
+ this.keywordReverseMap = new Map();
98
+ this.dataAlphabet = [];
99
+ this.dataReverseMap = new Map();
56
100
  this.isReady = false;
57
-
58
- if (EMOJI_UNIVERSE.length < TOTAL_TOKENS) {
59
- throw new Error(`CRITICAL: Not enough emojis. Need ${TOTAL_TOKENS}, have ${EMOJI_UNIVERSE.length}`);
60
- }
101
+ this.hmac = null;
102
+ this.lineLength = 0; // For wrapping
61
103
  }
62
104
 
63
- async init(existingSaltHex = null) {
105
+ async init(existingSaltHex = null, expectedAuthCheck = null) {
64
106
  this.salt = existingSaltHex
65
107
  ? Buffer.from(existingSaltHex, 'hex')
66
108
  : crypto.randomBytes(16);
67
109
 
68
110
  const derivedKey = await new Promise((resolve, reject) => {
69
- crypto.pbkdf2(this.password, this.salt, 100000, 36, 'sha256', (err, key) => {
111
+ crypto.pbkdf2(this.password, this.salt, 100000, 64, 'sha512', (err, key) => {
70
112
  if (err) reject(err); else resolve(key);
71
113
  });
72
114
  });
73
115
 
74
- const seedBuffer = derivedKey.subarray(0, 4);
75
- this.authHash = derivedKey.subarray(4);
76
- const seedInt = seedBuffer.readUInt32BE(0);
116
+ const seedBuffer = derivedKey.subarray(0, 32);
117
+ this.authKey = derivedKey.subarray(32, 64);
118
+
119
+ // Check password correctness immediately if Auth Check is provided
120
+ if (expectedAuthCheck) {
121
+ const calculatedAuthCheck = this.authKey.subarray(0, 4).toString('hex');
122
+ if (calculatedAuthCheck !== expectedAuthCheck) {
123
+ throw new Error("WRONG_PASSWORD");
124
+ }
125
+ }
77
126
 
78
- const shuffledEmojis = this._shuffleArray([...EMOJI_UNIVERSE], seedInt);
127
+ this.rng = new Xoshiro256(seedBuffer);
79
128
 
80
- let emojiIndex = 0;
81
- for (const keyword of C_KEYWORDS) {
82
- const emo = shuffledEmojis[emojiIndex++];
83
- this.tokenMap.set(keyword, emo);
84
- this.reverseMap.set(emo, keyword);
85
- }
129
+ const shuffled = this._shuffleArray([...RAW_UNIVERSE]);
86
130
 
87
- for (const char of ASCII_CHARS) {
88
- const emo = shuffledEmojis[emojiIndex++];
89
- this.tokenMap.set(char, emo);
90
- this.reverseMap.set(emo, char);
131
+ let ptr = 0;
132
+ this.keywordEmojis = [];
133
+ for (const kw of C_KEYWORDS) {
134
+ const emo = shuffled[ptr++];
135
+ this.keywordEmojis.push(emo);
136
+ this.keywordMap.set(kw, emo);
137
+ this.keywordReverseMap.set(emo, kw);
91
138
  }
92
139
 
93
- this.isReady = true;
94
- }
140
+ this.dataAlphabet = shuffled.slice(ptr, ptr + 1024);
141
+ if (this.dataAlphabet.length < 1024) throw new Error("Not enough emojis for Base-1024");
142
+
143
+ this.dataAlphabet.forEach((emo, idx) => {
144
+ this.dataReverseMap.set(emo, idx);
145
+ });
95
146
 
96
- _mulberry32(a) {
97
- return function() {
98
- var t = a += 0x6D2B79F5;
99
- t = Math.imul(t ^ t >>> 15, t | 1);
100
- t ^= t + Math.imul(t ^ t >>> 7, t | 61);
101
- return ((t ^ t >>> 14) >>> 0) / 4294967296;
102
- }
147
+ this.hmac = crypto.createHmac('sha256', this.authKey);
148
+ this.isReady = true;
103
149
  }
104
150
 
105
- _shuffleArray(array, seed) {
106
- const rng = this._mulberry32(seed);
151
+ _shuffleArray(array) {
107
152
  for (let i = array.length - 1; i > 0; i--) {
108
- const j = Math.floor(rng() * (i + 1));
153
+ const j = Math.floor(this.rng.nextFloat() * (i + 1));
109
154
  [array[i], array[j]] = [array[j], array[i]];
110
155
  }
111
156
  return array;
@@ -113,11 +158,10 @@ export class CipherEngine {
113
158
 
114
159
  _encodeHeader() {
115
160
  const saltHex = this.salt.toString('hex');
116
- const authHex = this.authHash.toString('hex');
117
- const fullHexString = saltHex + authHex;
118
-
161
+ const authCheck = this.authKey.subarray(0, 4).toString('hex'); // 4 bytes check
162
+
119
163
  let headerStr = '';
120
- for (const char of fullHexString) {
164
+ for (const char of (saltHex + authCheck)) {
121
165
  const val = parseInt(char, 16);
122
166
  headerStr += HEADER_ALPHABET[val];
123
167
  }
@@ -126,7 +170,6 @@ export class CipherEngine {
126
170
 
127
171
  static decodeHeader(headerStr) {
128
172
  let hexString = '';
129
- // Use segmenter here just in case moon emojis have variation selectors
130
173
  const segmenter = new Intl.Segmenter('en', { granularity: 'grapheme' });
131
174
  const segments = segmenter.segment(headerStr.trim());
132
175
 
@@ -135,119 +178,232 @@ export class CipherEngine {
135
178
  if (index === -1) throw new Error("Invalid Header format.");
136
179
  hexString += index.toString(16);
137
180
  }
138
- const saltHex = hexString.substring(0, 32);
139
- const authHex = hexString.substring(32);
140
- return { saltHex, authHex };
181
+
182
+ return {
183
+ saltHex: hexString.substring(0, 32),
184
+ authCheckHex: hexString.length >= 40 ? hexString.substring(32, 40) : null
185
+ };
141
186
  }
142
187
 
188
+ // --- STREAMING ENCRYPTION ---
189
+
143
190
  getEncryptStream() {
144
191
  if (!this.isReady) throw new Error("Engine not initialized");
145
192
  const engine = this;
146
- const decoder = new StringDecoder('utf8');
147
- let buffer = '';
148
-
193
+ let buffer = Buffer.alloc(0);
194
+
149
195
  return new Transform({
150
196
  transform(chunk, encoding, callback) {
151
- buffer += decoder.write(chunk);
197
+ const str = chunk.toString('utf8');
152
198
 
153
199
  const sortedKeywords = [...C_KEYWORDS].sort((a, b) => b.length - a.length);
154
200
  const keywordPattern = sortedKeywords.map(k => k.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')).join('|');
155
- const regex = new RegExp(`\\b(${keywordPattern})\\b|([\\s\\S])`, 'g');
201
+ const regex = new RegExp(`(${keywordPattern})`, 'g');
156
202
 
157
- let match;
158
- let output = '';
203
+ const parts = str.split(regex);
159
204
 
160
- while ((match = regex.exec(buffer)) !== null) {
161
- const token = match[0];
162
- if (engine.tokenMap.has(token)) {
163
- output += engine.tokenMap.get(token);
205
+ for (const part of parts) {
206
+ if (!part) continue;
207
+
208
+ if (C_KEYWORDS.includes(part)) {
209
+ if (buffer.length > 0) {
210
+ this.push(engine._flushDataBuffer(buffer));
211
+ buffer = Buffer.alloc(0);
212
+ }
213
+
214
+ const baseIdx = engine.keywordEmojis.indexOf(engine.keywordMap.get(part));
215
+ const shift = Number(engine.rng.next() % BigInt(engine.keywordEmojis.length));
216
+ const newIdx = (baseIdx + shift) % engine.keywordEmojis.length;
217
+ const polyEmoji = engine.keywordEmojis[newIdx];
218
+
219
+ const outBuf = Buffer.from(polyEmoji);
220
+ engine.hmac.update(outBuf);
221
+
222
+ // Emit wrapped
223
+ this.push(engine._wrapOutput(outBuf));
224
+
164
225
  } else {
165
- output += token;
226
+ buffer = Buffer.concat([buffer, Buffer.from(part, 'utf8')]);
227
+
228
+ while (buffer.length >= 5) {
229
+ const chunk = buffer.subarray(0, 5);
230
+ buffer = buffer.subarray(5);
231
+
232
+ const enc = engine._encodeBase1024(chunk);
233
+ engine.hmac.update(enc);
234
+ this.push(engine._wrapOutput(enc));
235
+ }
166
236
  }
167
237
  }
168
-
169
- buffer = '';
170
- this.push(output);
171
238
  callback();
172
239
  },
240
+
173
241
  flush(callback) {
174
- buffer += decoder.end();
175
- // Simple flush for MVP; in prod, we'd regex one last time.
242
+ if (buffer.length > 0) {
243
+ const padded = Buffer.alloc(5);
244
+ buffer.copy(padded);
245
+ const enc = engine._encodeBase1024(padded);
246
+ engine.hmac.update(enc);
247
+ this.push(engine._wrapOutput(enc));
248
+ }
249
+
250
+ const digest = engine.hmac.digest();
251
+ let footerStr = '';
252
+ for (const byte of digest) {
253
+ const hex = byte.toString(16).padStart(2, '0');
254
+ for (const char of hex) {
255
+ const val = parseInt(char, 16);
256
+ footerStr += HEADER_ALPHABET[val];
257
+ }
258
+ }
259
+ // Push footer (always starts on a new line or wrapped)
260
+ this.push(Buffer.from('\n' + footerStr));
176
261
  callback();
177
262
  }
178
263
  });
179
264
  }
180
265
 
266
+ _wrapOutput(bufferChunk) {
267
+ this.lineLength += bufferChunk.length;
268
+ if (this.lineLength > 300) {
269
+ this.lineLength = 0;
270
+ return Buffer.concat([bufferChunk, Buffer.from('\n')]);
271
+ }
272
+ return bufferChunk;
273
+ }
274
+
275
+ _encodeBase1024(buffer5) {
276
+ let val = 0n;
277
+ for (let i = 0; i < 5; i++) {
278
+ val += BigInt(buffer5[i]) * (256n ** BigInt(i));
279
+ }
280
+
281
+ let output = '';
282
+ for (let i = 0; i < 4; i++) {
283
+ const idx = Number(val % 1024n);
284
+ val = val / 1024n;
285
+ output += this.dataAlphabet[idx];
286
+ }
287
+ return Buffer.from(output);
288
+ }
289
+
290
+ _flushDataBuffer(buf) {
291
+ if (buf.length === 0) return Buffer.alloc(0);
292
+
293
+ const padded = Buffer.alloc(5);
294
+ buf.copy(padded);
295
+ const enc = this._encodeBase1024(padded);
296
+ this.hmac.update(enc);
297
+ return this._wrapOutput(enc);
298
+ }
299
+
300
+ // --- STREAMING DECRYPTION ---
301
+
181
302
  getDecryptStream() {
182
303
  if (!this.isReady) throw new Error("Engine not initialized");
183
304
  const engine = this;
184
- const decoder = new StringDecoder('utf8');
185
305
  const segmenter = new Intl.Segmenter('en', { granularity: 'grapheme' });
186
306
 
187
- let buffer = '';
307
+ const FOOTER_LEN = 64;
308
+ let emojiBuffer = [];
188
309
 
189
310
  return new Transform({
190
311
  transform(chunk, encoding, callback) {
191
- // 1. Decode bytes to string (handles partial UTF8 bytes)
192
- buffer += decoder.write(chunk);
193
-
194
- // 2. Segment into Graphemes (Emojis)
195
- const segments = [...segmenter.segment(buffer)];
312
+ const str = chunk.toString('utf8');
313
+ const segments = [...segmenter.segment(str)];
196
314
 
197
- // 3. Process all BUT the last segment
198
- // We keep the last segment in the buffer because it might be
199
- // the start of a multi-codepoint emoji that was split by the chunk boundary.
200
- // (e.g. Base char is here, Variation Selector is in next chunk)
201
-
202
- const processUntilIndex = segments.length > 1 ? segments.length - 1 : 0;
203
- let output = '';
204
- let processedString = '';
205
-
206
- // If we only have 1 segment, we can't be sure it's complete, wait for next chunk
207
- // UNLESS the buffer is getting huge, then force it.
208
- if (segments.length === 1 && buffer.length < 100) {
209
- // Wait for more data
210
- callback();
211
- return;
212
- }
315
+ for (const { segment } of segments) {
316
+ // Filter out whitespace/newlines used for wrapping
317
+ if (segment.match(/\s/)) continue;
213
318
 
214
- // Process safe segments
215
- for (let i = 0; i < processUntilIndex; i++) {
216
- const char = segments[i].segment;
217
- processedString += char;
319
+ emojiBuffer.push(segment);
218
320
 
219
- if (engine.reverseMap.has(char)) {
220
- output += engine.reverseMap.get(char);
221
- } else {
222
- output += char;
321
+ if (emojiBuffer.length > FOOTER_LEN) {
322
+ const emo = emojiBuffer.shift();
323
+ engine._processDecryptToken(emo, this);
324
+ engine.hmac.update(Buffer.from(emo));
223
325
  }
224
326
  }
225
-
226
- // Update buffer to only contain the remaining tail
227
- // Note: buffer might contain bytes not yet in segments if StringDecoder held them?
228
- // No, decoder.write returns what is available.
229
- // We just need to remove the processed part from the buffer string.
230
- if (processUntilIndex > 0) {
231
- buffer = buffer.slice(processedString.length);
232
- this.push(output);
233
- }
234
-
235
327
  callback();
236
328
  },
329
+
237
330
  flush(callback) {
238
- // Process any remaining tail (buffer + decoder leftovers)
239
- buffer += decoder.end();
240
- if (buffer) {
241
- const segments = segmenter.segment(buffer);
242
- let output = '';
243
- for (const { segment } of segments) {
244
- if (engine.reverseMap.has(segment)) output += engine.reverseMap.get(segment);
245
- else output += segment;
331
+ if (emojiBuffer.length !== FOOTER_LEN) {
332
+ this.emit('error', new Error("File corrupted or truncated (No Footer)"));
333
+ return;
334
+ }
335
+
336
+ const footerStr = emojiBuffer.join('');
337
+ const calcDigest = engine.hmac.digest('hex');
338
+
339
+ let footerHex = '';
340
+ try {
341
+ for (const char of footerStr) {
342
+ const idx = HEADER_ALPHABET.indexOf(char);
343
+ if (idx === -1) throw new Error();
344
+ footerHex += idx.toString(16);
246
345
  }
247
- this.push(output);
346
+ } catch (e) {
347
+ this.emit('error', new Error("Invalid Integrity Seal"));
348
+ return;
349
+ }
350
+
351
+ if (footerHex !== calcDigest) {
352
+ // Critical security failure
353
+ this.emit('error', new Error("FILE_TAMPERED"));
354
+ return;
355
+ }
356
+
357
+ if (engine.decodeDataBuf.length > 0) {
248
358
  }
249
359
  callback();
250
360
  }
251
361
  });
252
362
  }
363
+
364
+ decodeDataBuf = [];
365
+
366
+ _processDecryptToken(emo, stream) {
367
+ if (this.keywordReverseMap.has(emo)) {
368
+ const currentR = Number(this.rng.next() % BigInt(this.keywordEmojis.length));
369
+ const emoIdx = this.keywordEmojis.indexOf(emo);
370
+
371
+ let baseIdx = (emoIdx - currentR) % this.keywordEmojis.length;
372
+ if (baseIdx < 0) baseIdx += this.keywordEmojis.length;
373
+
374
+ const originalEmo = this.keywordEmojis[baseIdx];
375
+ const keyword = this.keywordReverseMap.get(originalEmo);
376
+
377
+ if (this.decodeDataBuf.length > 0) {
378
+ this.decodeDataBuf = [];
379
+ }
380
+
381
+ stream.push(keyword);
382
+
383
+ } else if (this.dataReverseMap.has(emo)) {
384
+ this.decodeDataBuf.push(this.dataReverseMap.get(emo));
385
+ if (this.decodeDataBuf.length === 4) {
386
+ const chunk = this._decodeBase1024(this.decodeDataBuf);
387
+ this.decodeDataBuf = [];
388
+ const cleanChunk = chunk.filter(b => b !== 0x00);
389
+ stream.push(cleanChunk);
390
+ }
391
+ } else {
392
+ // Unknown emojis are ignored now
393
+ }
394
+ }
395
+
396
+ _decodeBase1024(indices) {
397
+ let val = 0n;
398
+ for (let i = 3; i >= 0; i--) {
399
+ val = (val * 1024n) + BigInt(indices[i]);
400
+ }
401
+
402
+ const buf = Buffer.alloc(5);
403
+ for (let i = 0; i < 5; i++) {
404
+ buf[i] = Number(val % 256n);
405
+ val = val / 256n;
406
+ }
407
+ return buf;
408
+ }
253
409
  }
package/package.json CHANGED
@@ -1,20 +1,28 @@
1
1
  {
2
2
  "name": "mojic",
3
- "version": "1.0.2",
4
- "description": "Obfuscate C source code into emojis using password-seeded mapping",
3
+ "version": "1.1.0",
4
+ "description": "Obfuscate C source code into encrypted, password-seeded emoji streams.",
5
5
  "main": "bin/mojic.js",
6
6
  "bin": {
7
7
  "mojic": "bin/mojic.js"
8
8
  },
9
9
  "type": "module",
10
10
  "scripts": {
11
- "start": "node bin/mojic.js"
11
+ "start": "node bin/mojic.js",
12
+ "build-binaries": "pkg . --out-path dist --public"
13
+ },
14
+ "repository": {
15
+ "type": "git",
16
+ "url": "git+https://github.com/notamitgamer/mojic.git"
12
17
  },
13
18
  "dependencies": {
14
19
  "chalk": "^5.3.0",
15
20
  "commander": "^11.1.0",
16
21
  "inquirer": "^9.2.12"
17
22
  },
23
+ "devDependencies": {
24
+ "pkg": "^5.8.1"
25
+ },
18
26
  "engines": {
19
27
  "node": ">=18.0.0"
20
28
  },
@@ -24,8 +32,22 @@
24
32
  "emoji",
25
33
  "obfuscation",
26
34
  "c",
27
- "security"
35
+ "security",
36
+ "polymorphic"
28
37
  ],
29
- "author": "",
30
- "license": "Apache-2.0"
38
+ "author": "notamitgamer",
39
+ "license": "Apache-2.0",
40
+ "pkg": {
41
+ "scripts": "bin/mojic.js",
42
+ "assets": [
43
+ "lib/**/*",
44
+ "package.json"
45
+ ],
46
+ "targets": [
47
+ "node18-win-x64",
48
+ "node18-linux-x64",
49
+ "node18-macos-x64"
50
+ ],
51
+ "outputPath": "dist"
52
+ }
31
53
  }