mojic 1.0.3 → 1.2.4

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.2.4
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.3')
15
+ .version('1.2.4')
16
16
  .addHelpCommand('help [command]', 'Display help for command')
17
17
  .showHelpAfterError();
18
18
 
@@ -78,11 +78,20 @@ const createHeaderSkipper = () => {
78
78
  });
79
79
  };
80
80
 
81
- // --- Recursive Logic ---
81
+ const createMinifier = () => {
82
+ return new Transform({
83
+ transform(chunk, encoding, cb) {
84
+ let str = chunk.toString();
85
+ str = str.replace(/\r?\n|\r/g, ' ');
86
+ str = str.replace(/\s+/g, ' ');
87
+ this.push(str);
88
+ cb();
89
+ }
90
+ });
91
+ };
82
92
 
83
93
  async function traverseDirectory(currentPath, extension, callback) {
84
94
  const entries = fs.readdirSync(currentPath, { withFileTypes: true });
85
-
86
95
  for (const entry of entries) {
87
96
  const fullPath = path.join(currentPath, entry.name);
88
97
  if (entry.isDirectory()) {
@@ -99,18 +108,20 @@ program
99
108
  .command('encode')
100
109
  .argument('<path>', 'File or Directory to encode')
101
110
  .option('-r, --recursive', 'Recursively encrypt all .c files in directory')
111
+ .option('-f, --flat', 'Flatten structure (strip whitespace/newlines) before encrypting')
102
112
  .description('Encrypt a file or directory into emojis')
103
113
  .action(async (targetPath, options) => {
104
114
  try {
105
115
  if (!fs.existsSync(targetPath)) throw new Error('Path not found');
106
116
  const stats = fs.statSync(targetPath);
107
117
 
108
- // Validation
109
118
  if (stats.isDirectory() && !options.recursive) {
110
119
  throw new Error(`'${targetPath}' is a directory. Use -r to process recursively.`);
111
120
  }
112
121
 
113
- console.log(chalk.blue('🔒 Initiating Mojic Encryption...'));
122
+ console.log(chalk.blue('Initiating Mojic Encryption v1.2...'));
123
+ if (options.flat) console.log(chalk.yellow(' -> Structural Flattening Enabled'));
124
+
114
125
  const password = await promptPassword('Create password for file(s):');
115
126
 
116
127
  const processFile = async (filePath) => {
@@ -118,7 +129,7 @@ program
118
129
  console.log(chalk.dim(` Processing: ${path.basename(filePath)} -> ${path.basename(outputName)}`));
119
130
 
120
131
  const engine = new CipherEngine(password);
121
- await engine.init(); // Unique salt per file
132
+ await engine.init();
122
133
 
123
134
  const readStream = fs.createReadStream(filePath);
124
135
  const writeStream = fs.createWriteStream(outputName);
@@ -126,7 +137,10 @@ program
126
137
  writeStream.write(engine._encodeHeader());
127
138
 
128
139
  await new Promise((resolve, reject) => {
129
- readStream
140
+ let pipeline = readStream;
141
+ if (options.flat) pipeline = pipeline.pipe(createMinifier());
142
+
143
+ pipeline
130
144
  .pipe(engine.getEncryptStream())
131
145
  .pipe(writeStream)
132
146
  .on('finish', resolve)
@@ -135,12 +149,12 @@ program
135
149
  };
136
150
 
137
151
  if (stats.isDirectory()) {
138
- console.log(chalk.blue(`📂 Scanning directory: ${targetPath}`));
152
+ console.log(chalk.blue(`Scanning directory: ${targetPath}`));
139
153
  await traverseDirectory(targetPath, '.c', processFile);
140
- console.log(chalk.green('Batch encryption complete.'));
154
+ console.log(chalk.green('Batch encryption complete.'));
141
155
  } else {
142
156
  await processFile(targetPath);
143
- console.log(chalk.green(`✅ Encrypted.`));
157
+ console.log(chalk.green(`Encrypted.`));
144
158
  }
145
159
 
146
160
  } catch (err) {
@@ -162,8 +176,8 @@ program
162
176
  throw new Error(`'${targetPath}' is a directory. Use -r to process recursively.`);
163
177
  }
164
178
 
165
- console.log(chalk.blue('🔓 Initiating Decryption...'));
166
- const password = await promptPassword('Enter password:'); // Assume same pass for dir
179
+ console.log(chalk.blue('Initiating Decryption...'));
180
+ const password = await promptPassword('Enter password:');
167
181
 
168
182
  const processFile = async (filePath) => {
169
183
  try {
@@ -172,38 +186,51 @@ program
172
186
 
173
187
  const headerStr = await getStreamHeader(filePath);
174
188
  const metadata = CipherEngine.decodeHeader(headerStr);
175
-
176
189
  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
- }
190
+ await engine.init(metadata.saltHex, metadata.authCheckHex);
183
191
 
184
192
  const readStream = fs.createReadStream(filePath);
185
193
  const writeStream = fs.createWriteStream(outputName);
186
194
 
187
195
  await new Promise((resolve, reject) => {
196
+ const decryptStream = engine.getDecryptStream();
197
+ decryptStream.on('error', (err) => {
198
+ readStream.destroy(); // Stop reading
199
+ writeStream.destroy(); // Stop writing
200
+ reject(err);
201
+ });
202
+
188
203
  readStream
189
204
  .pipe(createHeaderSkipper())
190
- .pipe(engine.getDecryptStream())
205
+ .pipe(decryptStream)
191
206
  .pipe(writeStream)
192
207
  .on('finish', resolve)
193
208
  .on('error', reject);
194
209
  });
210
+ return true;
195
211
  } catch (e) {
196
- console.log(chalk.red(` ⚠️ Error processing ${path.basename(filePath)}: ${e.message}`));
212
+ const outputName = filePath.replace(/\.mojic$/, '') + '.restored.c';
213
+ // Wait for stream handles to release
214
+ setTimeout(() => { if (fs.existsSync(outputName)) try { fs.unlinkSync(outputName); } catch(ign){} }, 100);
215
+
216
+ if (e.message === "WRONG_PASSWORD") {
217
+ console.log(chalk.red(` Error: Incorrect Password`));
218
+ } else if (e.message === "FILE_TAMPERED") {
219
+ console.log(chalk.red(` Error: File Tampered! Integrity check failed.`));
220
+ } else {
221
+ console.log(chalk.red(` Error: ${e.message}`));
222
+ }
223
+ return false;
197
224
  }
198
225
  };
199
226
 
200
227
  if (stats.isDirectory()) {
201
- console.log(chalk.blue(`📂 Scanning directory: ${targetPath}`));
228
+ console.log(chalk.blue(`Scanning directory: ${targetPath}`));
202
229
  await traverseDirectory(targetPath, '.mojic', processFile);
203
- console.log(chalk.green('Batch decryption complete.'));
230
+ console.log(chalk.green('Batch decryption complete.'));
204
231
  } else {
205
- await processFile(targetPath);
206
- console.log(chalk.green(`✅ Restored.`));
232
+ const success = await processFile(targetPath);
233
+ if (success) console.log(chalk.green(`Restored.`));
207
234
  }
208
235
 
209
236
  } catch (err) {
@@ -211,32 +238,23 @@ program
211
238
  }
212
239
  });
213
240
 
214
- // --- Security Rotation Tools (SRT) ---
215
-
241
+ // --- SRT ---
216
242
  const rotatePassword = async (file) => {
217
243
  try {
218
244
  if (!fs.existsSync(file)) throw new Error('File not found');
219
- console.log(chalk.yellow(`🔄 Rotating Password for ${path.basename(file)}...`));
245
+ console.log(chalk.yellow(`Rotating Password for ${path.basename(file)}...`));
220
246
 
221
- // 1. Authenticate Old
222
247
  const headerStr = await getStreamHeader(file);
223
248
  const metadata = CipherEngine.decodeHeader(headerStr);
224
249
  const oldPass = await promptPassword('Enter CURRENT password:');
225
250
 
226
251
  const oldEngine = new CipherEngine(oldPass);
227
- await oldEngine.init(metadata.saltHex);
228
-
229
- if (oldEngine.authHash.toString('hex') !== metadata.authHex) {
230
- console.error(chalk.red('❌ Incorrect Current Password'));
231
- process.exit(1);
232
- }
252
+ await oldEngine.init(metadata.saltHex, metadata.authCheckHex);
233
253
 
234
- // 2. Init New
235
254
  const newPass = await promptPassword('Enter NEW password:');
236
255
  const newEngine = new CipherEngine(newPass);
237
256
  await newEngine.init();
238
257
 
239
- // 3. Process
240
258
  const tempFile = file + '.tmp';
241
259
  const readStream = fs.createReadStream(file);
242
260
  const writeStream = fs.createWriteStream(tempFile);
@@ -244,46 +262,36 @@ const rotatePassword = async (file) => {
244
262
  writeStream.write(newEngine._encodeHeader());
245
263
 
246
264
  await new Promise((resolve, reject) => {
247
- readStream
248
- .pipe(createHeaderSkipper())
249
- .pipe(oldEngine.getDecryptStream())
250
- .pipe(newEngine.getEncryptStream())
251
- .pipe(writeStream)
252
- .on('finish', resolve)
253
- .on('error', reject);
265
+ const decryptStream = oldEngine.getDecryptStream();
266
+ decryptStream.on('error', reject);
267
+ readStream.pipe(createHeaderSkipper()).pipe(decryptStream).pipe(newEngine.getEncryptStream()).pipe(writeStream).on('finish', resolve).on('error', reject);
254
268
  });
255
269
 
256
270
  fs.renameSync(tempFile, file);
257
- console.log(chalk.green(`✅ Password updated.`));
271
+ console.log(chalk.green(`Password updated.`));
258
272
 
259
273
  } catch (err) {
260
- console.error(chalk.red('Error:'), err.message);
274
+ if (fs.existsSync(file + '.tmp')) fs.unlinkSync(file + '.tmp');
275
+ if (err.message === "WRONG_PASSWORD") console.error(chalk.red('Error: Incorrect Current Password'));
276
+ else console.error(chalk.red('Error:'), err.message);
261
277
  }
262
278
  };
263
279
 
264
280
  const reEncrypt = async (file) => {
265
281
  try {
266
282
  if (!fs.existsSync(file)) throw new Error('File not found');
267
- console.log(chalk.yellow(`🎲 Re-shuffling Entropy for ${path.basename(file)}...`));
283
+ console.log(chalk.yellow(`Re-shuffling Entropy for ${path.basename(file)}...`));
268
284
 
269
- // 1. Authenticate
270
285
  const headerStr = await getStreamHeader(file);
271
286
  const metadata = CipherEngine.decodeHeader(headerStr);
272
287
  const password = await promptPassword('Enter password:');
273
288
 
274
289
  const oldEngine = new CipherEngine(password);
275
- await oldEngine.init(metadata.saltHex);
290
+ await oldEngine.init(metadata.saltHex, metadata.authCheckHex);
276
291
 
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
292
  const newEngine = new CipherEngine(password);
284
293
  await newEngine.init();
285
294
 
286
- // 3. Process
287
295
  const tempFile = file + '.tmp';
288
296
  const readStream = fs.createReadStream(file);
289
297
  const writeStream = fs.createWriteStream(tempFile);
@@ -291,50 +299,25 @@ const reEncrypt = async (file) => {
291
299
  writeStream.write(newEngine._encodeHeader());
292
300
 
293
301
  await new Promise((resolve, reject) => {
294
- readStream
295
- .pipe(createHeaderSkipper())
296
- .pipe(oldEngine.getDecryptStream())
297
- .pipe(newEngine.getEncryptStream())
298
- .pipe(writeStream)
299
- .on('finish', resolve)
300
- .on('error', reject);
302
+ const decryptStream = oldEngine.getDecryptStream();
303
+ decryptStream.on('error', reject);
304
+ readStream.pipe(createHeaderSkipper()).pipe(decryptStream).pipe(newEngine.getEncryptStream()).pipe(writeStream).on('finish', resolve).on('error', reject);
301
305
  });
302
306
 
303
307
  fs.renameSync(tempFile, file);
304
- console.log(chalk.green(`✅ File re-encrypted.`));
308
+ console.log(chalk.green(`File re-encrypted.`));
305
309
 
306
310
  } catch (err) {
307
- console.error(chalk.red('Error:'), err.message);
311
+ if (fs.existsSync(file + '.tmp')) fs.unlinkSync(file + '.tmp');
312
+ if (err.message === "WRONG_PASSWORD") console.error(chalk.red('Error: Incorrect Password'));
313
+ else console.error(chalk.red('Error:'), err.message);
308
314
  }
309
315
  };
310
316
 
311
- program
312
- .command('srt')
313
- .description('Security and Rotation Tools')
314
- .option('--pass <file>', 'Update the password for an existing file')
315
- .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
- .action(async (options) => {
322
- if (options.pass) {
323
- await rotatePassword(options.pass);
324
- } else if (options.re) {
325
- await reEncrypt(options.re);
326
- } else {
327
- console.log(chalk.yellow('Please specify an option: --pass <file> or --re <file>'));
328
- program.commands.find(c => c.name() === 'srt').help();
329
- }
330
- });
331
-
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
- `);
317
+ program.command('srt').description('Security and Rotation Tools').option('--pass <file>', 'Update password').option('--re <file>', 'Re-encrypt').action(async (options) => {
318
+ if (options.pass) await rotatePassword(options.pass);
319
+ else if (options.re) await reEncrypt(options.re);
320
+ else { console.log(chalk.yellow('Please specify an option: --pass <file> or --re <file>')); program.commands.find(c => c.name() === 'srt').help(); }
321
+ });
339
322
 
340
323
  program.parse(process.argv);
@@ -3,109 +3,150 @@ 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.2.4 CIPHER ENGINE
7
+ * "Operation Polymorphic Chaos"
8
+ * * Fixes:
9
+ * - Word Boundaries: Prevents splitting variables like 'secretCode'
10
+ * - Regex: correctly handles #directives vs keywords
11
11
  */
12
12
 
13
+ // --- EMOJI UNIVERSE GENERATION ---
13
14
  const HEADER_ALPHABET = ['🌑', '🌒', '🌓', '🌔', '🌕', '🌖', '🌗', '🌘', '🕐', '🕑', '🕒', '🕓', '🕔', '🕕', '🕖', '🕗'];
14
15
 
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
- ];
16
+ const generateUniverse = () => {
17
+ const universe = [];
18
+ const ranges = [
19
+ [0x1F600, 0x1F64F], // Emoticons
20
+ [0x1F300, 0x1F5FF], // Misc Symbols
21
+ [0x1F680, 0x1F6FF], // Transport
22
+ [0x1F900, 0x1F9FF] // Supplemental
23
+ ];
24
+
25
+ const headerSet = new Set(HEADER_ALPHABET);
26
+
27
+ for (const [start, end] of ranges) {
28
+ for (let code = start; code <= end; code++) {
29
+ const char = String.fromCodePoint(code);
30
+ if (!headerSet.has(char)) {
31
+ universe.push(char);
32
+ }
33
+ }
34
+ }
35
+ return universe; // Expect > 1100 chars
36
+ };
37
+
38
+ const RAW_UNIVERSE = generateUniverse();
39
+
40
+ if (RAW_UNIVERSE.length < 1080) {
41
+ throw new Error("Critical: Emoji Universe generation failed to produce enough tokens.");
42
+ }
37
43
 
38
44
  const C_KEYWORDS = [
39
45
  'auto', 'break', 'case', 'char', 'const', 'continue', 'default', 'do',
40
46
  'double', 'else', 'enum', 'extern', 'float', 'for', 'goto', 'if',
41
47
  'int', 'long', 'register', 'return', 'short', 'signed', 'sizeof', 'static',
42
48
  'struct', 'switch', 'typedef', 'union', 'unsigned', 'void', 'volatile', 'while',
43
- 'include', 'define', 'main', 'printf', 'NULL'
49
+ 'include', 'define', 'main', 'printf', 'NULL', '#include', '#define'
44
50
  ];
45
51
 
46
- const ASCII_CHARS = Array.from({ length: 95 }, (_, i) => String.fromCharCode(i + 32))
47
- .concat(['\n', '\t', '\r']);
52
+ // --- PRNG: Xoshiro256** ---
53
+ class Xoshiro256 {
54
+ constructor(seedBuffer) {
55
+ if (seedBuffer.length < 32) throw new Error("Seed too short");
56
+ this.s = [
57
+ seedBuffer.readBigUInt64BE(0),
58
+ seedBuffer.readBigUInt64BE(8),
59
+ seedBuffer.readBigUInt64BE(16),
60
+ seedBuffer.readBigUInt64BE(24)
61
+ ];
62
+ }
63
+
64
+ next() {
65
+ const result = this.rotl(this.s[1] * 5n, 7n) * 9n;
66
+ const t = this.s[1] << 17n;
48
67
 
49
- const TOTAL_TOKENS = C_KEYWORDS.length + ASCII_CHARS.length;
68
+ this.s[2] ^= this.s[0];
69
+ this.s[3] ^= this.s[1];
70
+ this.s[1] ^= this.s[2];
71
+ this.s[0] ^= this.s[3];
72
+
73
+ this.s[2] ^= t;
74
+ this.s[3] = this.rotl(this.s[3], 45n);
75
+
76
+ return result;
77
+ }
78
+
79
+ nextFloat() {
80
+ const val = Number(this.next() >> 11n);
81
+ return val * (2 ** -53);
82
+ }
83
+
84
+ rotl(x, k) {
85
+ return (x << k) | (x >> (64n - k));
86
+ }
87
+ }
50
88
 
51
89
  export class CipherEngine {
52
90
  constructor(password) {
53
91
  this.password = password;
54
- this.tokenMap = new Map();
55
- this.reverseMap = new Map();
92
+ this.keywordMap = new Map();
93
+ this.keywordReverseMap = new Map();
94
+ this.dataAlphabet = [];
95
+ this.dataReverseMap = new Map();
56
96
  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
- }
97
+ this.hmac = null;
98
+ this.lineLength = 0; // For wrapping
61
99
  }
62
100
 
63
- async init(existingSaltHex = null) {
101
+ async init(existingSaltHex = null, expectedAuthCheck = null) {
64
102
  this.salt = existingSaltHex
65
103
  ? Buffer.from(existingSaltHex, 'hex')
66
104
  : crypto.randomBytes(16);
67
105
 
68
106
  const derivedKey = await new Promise((resolve, reject) => {
69
- crypto.pbkdf2(this.password, this.salt, 100000, 36, 'sha256', (err, key) => {
107
+ crypto.pbkdf2(this.password, this.salt, 100000, 64, 'sha512', (err, key) => {
70
108
  if (err) reject(err); else resolve(key);
71
109
  });
72
110
  });
73
111
 
74
- const seedBuffer = derivedKey.subarray(0, 4);
75
- this.authHash = derivedKey.subarray(4);
76
- const seedInt = seedBuffer.readUInt32BE(0);
112
+ const seedBuffer = derivedKey.subarray(0, 32);
113
+ this.authKey = derivedKey.subarray(32, 64);
114
+
115
+ // Check password correctness immediately if Auth Check is provided
116
+ if (expectedAuthCheck) {
117
+ const calculatedAuthCheck = this.authKey.subarray(0, 4).toString('hex');
118
+ if (calculatedAuthCheck !== expectedAuthCheck) {
119
+ throw new Error("WRONG_PASSWORD");
120
+ }
121
+ }
122
+
123
+ this.rng = new Xoshiro256(seedBuffer);
77
124
 
78
- const shuffledEmojis = this._shuffleArray([...EMOJI_UNIVERSE], seedInt);
125
+ const shuffled = this._shuffleArray([...RAW_UNIVERSE]);
79
126
 
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);
127
+ let ptr = 0;
128
+ this.keywordEmojis = [];
129
+ for (const kw of C_KEYWORDS) {
130
+ const emo = shuffled[ptr++];
131
+ this.keywordEmojis.push(emo);
132
+ this.keywordMap.set(kw, emo);
133
+ this.keywordReverseMap.set(emo, kw);
85
134
  }
86
135
 
87
- for (const char of ASCII_CHARS) {
88
- const emo = shuffledEmojis[emojiIndex++];
89
- this.tokenMap.set(char, emo);
90
- this.reverseMap.set(emo, char);
91
- }
136
+ this.dataAlphabet = shuffled.slice(ptr, ptr + 1024);
137
+ if (this.dataAlphabet.length < 1024) throw new Error("Not enough emojis for Base-1024");
138
+
139
+ this.dataAlphabet.forEach((emo, idx) => {
140
+ this.dataReverseMap.set(emo, idx);
141
+ });
92
142
 
143
+ this.hmac = crypto.createHmac('sha256', this.authKey);
93
144
  this.isReady = true;
94
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
- }
103
- }
104
-
105
- _shuffleArray(array, seed) {
106
- const rng = this._mulberry32(seed);
147
+ _shuffleArray(array) {
107
148
  for (let i = array.length - 1; i > 0; i--) {
108
- const j = Math.floor(rng() * (i + 1));
149
+ const j = Math.floor(this.rng.nextFloat() * (i + 1));
109
150
  [array[i], array[j]] = [array[j], array[i]];
110
151
  }
111
152
  return array;
@@ -113,11 +154,10 @@ export class CipherEngine {
113
154
 
114
155
  _encodeHeader() {
115
156
  const saltHex = this.salt.toString('hex');
116
- const authHex = this.authHash.toString('hex');
117
- const fullHexString = saltHex + authHex;
118
-
157
+ const authCheck = this.authKey.subarray(0, 4).toString('hex');
158
+
119
159
  let headerStr = '';
120
- for (const char of fullHexString) {
160
+ for (const char of (saltHex + authCheck)) {
121
161
  const val = parseInt(char, 16);
122
162
  headerStr += HEADER_ALPHABET[val];
123
163
  }
@@ -126,7 +166,6 @@ export class CipherEngine {
126
166
 
127
167
  static decodeHeader(headerStr) {
128
168
  let hexString = '';
129
- // Use segmenter here just in case moon emojis have variation selectors
130
169
  const segmenter = new Intl.Segmenter('en', { granularity: 'grapheme' });
131
170
  const segments = segmenter.segment(headerStr.trim());
132
171
 
@@ -135,119 +174,226 @@ export class CipherEngine {
135
174
  if (index === -1) throw new Error("Invalid Header format.");
136
175
  hexString += index.toString(16);
137
176
  }
138
- const saltHex = hexString.substring(0, 32);
139
- const authHex = hexString.substring(32);
140
- return { saltHex, authHex };
177
+
178
+ return {
179
+ saltHex: hexString.substring(0, 32),
180
+ authCheckHex: hexString.length >= 40 ? hexString.substring(32, 40) : null
181
+ };
141
182
  }
142
183
 
184
+ // --- STREAMING ENCRYPTION ---
185
+
143
186
  getEncryptStream() {
144
187
  if (!this.isReady) throw new Error("Engine not initialized");
145
188
  const engine = this;
146
- const decoder = new StringDecoder('utf8');
147
- let buffer = '';
148
-
189
+ let buffer = Buffer.alloc(0);
190
+
149
191
  return new Transform({
150
192
  transform(chunk, encoding, callback) {
151
- buffer += decoder.write(chunk);
193
+ const str = chunk.toString('utf8');
152
194
 
153
- const sortedKeywords = [...C_KEYWORDS].sort((a, b) => b.length - a.length);
154
- const keywordPattern = sortedKeywords.map(k => k.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')).join('|');
155
- const regex = new RegExp(`\\b(${keywordPattern})\\b|([\\s\\S])`, 'g');
195
+ // --- FIXED REGEX LOGIC ---
196
+ // Separate alpha keywords (int, void) from symbols (#include)
197
+ const alphaKeywords = C_KEYWORDS.filter(k => /^\w+$/.test(k)).sort((a,b)=>b.length-a.length).join('|');
198
+ const symKeywords = C_KEYWORDS.filter(k => !/^\w+$/.test(k)).map(k => k.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')).join('|');
156
199
 
157
- let match;
158
- let output = '';
200
+ // Match: \b(int|void)\b OR (#include)
201
+ // This ensures 'secretCode' isn't matched as 'Code' containing 'do' or 'int'
202
+ const regex = new RegExp(`(\\b(?:${alphaKeywords})\\b|(?:${symKeywords}))`, 'g');
159
203
 
160
- while ((match = regex.exec(buffer)) !== null) {
161
- const token = match[0];
162
- if (engine.tokenMap.has(token)) {
163
- output += engine.tokenMap.get(token);
204
+ const parts = str.split(regex);
205
+
206
+ for (const part of parts) {
207
+ if (!part) continue;
208
+
209
+ if (C_KEYWORDS.includes(part)) {
210
+ if (buffer.length > 0) {
211
+ this.push(engine._flushDataBuffer(buffer));
212
+ buffer = Buffer.alloc(0);
213
+ }
214
+
215
+ const baseIdx = engine.keywordEmojis.indexOf(engine.keywordMap.get(part));
216
+ const shift = Number(engine.rng.next() % BigInt(engine.keywordEmojis.length));
217
+ const newIdx = (baseIdx + shift) % engine.keywordEmojis.length;
218
+ const polyEmoji = engine.keywordEmojis[newIdx];
219
+
220
+ const outBuf = Buffer.from(polyEmoji);
221
+ engine.hmac.update(outBuf);
222
+ this.push(engine._wrapOutput(outBuf));
223
+
164
224
  } else {
165
- output += token;
225
+ buffer = Buffer.concat([buffer, Buffer.from(part, 'utf8')]);
226
+
227
+ while (buffer.length >= 5) {
228
+ const chunk = buffer.subarray(0, 5);
229
+ buffer = buffer.subarray(5);
230
+
231
+ const enc = engine._encodeBase1024(chunk);
232
+ engine.hmac.update(enc);
233
+ this.push(engine._wrapOutput(enc));
234
+ }
166
235
  }
167
236
  }
168
-
169
- buffer = '';
170
- this.push(output);
171
237
  callback();
172
238
  },
239
+
173
240
  flush(callback) {
174
- buffer += decoder.end();
175
- // Simple flush for MVP; in prod, we'd regex one last time.
241
+ if (buffer.length > 0) {
242
+ const padded = Buffer.alloc(5);
243
+ buffer.copy(padded);
244
+ const enc = engine._encodeBase1024(padded);
245
+ engine.hmac.update(enc);
246
+ this.push(engine._wrapOutput(enc));
247
+ }
248
+
249
+ const digest = engine.hmac.digest();
250
+ let footerStr = '';
251
+ for (const byte of digest) {
252
+ const hex = byte.toString(16).padStart(2, '0');
253
+ for (const char of hex) {
254
+ const val = parseInt(char, 16);
255
+ footerStr += HEADER_ALPHABET[val];
256
+ }
257
+ }
258
+ this.push(Buffer.from('\n' + footerStr));
176
259
  callback();
177
260
  }
178
261
  });
179
262
  }
180
263
 
264
+ _wrapOutput(bufferChunk) {
265
+ this.lineLength += bufferChunk.length;
266
+ if (this.lineLength > 300) {
267
+ this.lineLength = 0;
268
+ return Buffer.concat([bufferChunk, Buffer.from('\n')]);
269
+ }
270
+ return bufferChunk;
271
+ }
272
+
273
+ _encodeBase1024(buffer5) {
274
+ let val = 0n;
275
+ for (let i = 0; i < 5; i++) {
276
+ val += BigInt(buffer5[i]) * (256n ** BigInt(i));
277
+ }
278
+
279
+ let output = '';
280
+ for (let i = 0; i < 4; i++) {
281
+ const idx = Number(val % 1024n);
282
+ val = val / 1024n;
283
+ output += this.dataAlphabet[idx];
284
+ }
285
+ return Buffer.from(output);
286
+ }
287
+
288
+ _flushDataBuffer(buf) {
289
+ if (buf.length === 0) return Buffer.alloc(0);
290
+ const padded = Buffer.alloc(5);
291
+ buf.copy(padded);
292
+ const enc = this._encodeBase1024(padded);
293
+ this.hmac.update(enc);
294
+ return this._wrapOutput(enc);
295
+ }
296
+
297
+ // --- STREAMING DECRYPTION ---
298
+
181
299
  getDecryptStream() {
182
300
  if (!this.isReady) throw new Error("Engine not initialized");
183
301
  const engine = this;
184
- const decoder = new StringDecoder('utf8');
185
302
  const segmenter = new Intl.Segmenter('en', { granularity: 'grapheme' });
186
303
 
187
- let buffer = '';
304
+ const FOOTER_LEN = 64;
305
+ let emojiBuffer = [];
188
306
 
189
307
  return new Transform({
190
308
  transform(chunk, encoding, callback) {
191
- // 1. Decode bytes to string (handles partial UTF8 bytes)
192
- buffer += decoder.write(chunk);
309
+ const str = chunk.toString('utf8');
310
+ const segments = [...segmenter.segment(str)];
193
311
 
194
- // 2. Segment into Graphemes (Emojis)
195
- const segments = [...segmenter.segment(buffer)];
196
-
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
- }
312
+ for (const { segment } of segments) {
313
+ if (segment.match(/\s/)) continue; // Skip wraps
213
314
 
214
- // Process safe segments
215
- for (let i = 0; i < processUntilIndex; i++) {
216
- const char = segments[i].segment;
217
- processedString += char;
315
+ emojiBuffer.push(segment);
218
316
 
219
- if (engine.reverseMap.has(char)) {
220
- output += engine.reverseMap.get(char);
221
- } else {
222
- output += char;
317
+ if (emojiBuffer.length > FOOTER_LEN) {
318
+ const emo = emojiBuffer.shift();
319
+ engine._processDecryptToken(emo, this);
320
+ engine.hmac.update(Buffer.from(emo));
223
321
  }
224
322
  }
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
323
  callback();
236
324
  },
325
+
237
326
  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;
327
+ if (emojiBuffer.length !== FOOTER_LEN) {
328
+ this.emit('error', new Error("File corrupted or truncated (No Footer)"));
329
+ return;
330
+ }
331
+
332
+ const footerStr = emojiBuffer.join('');
333
+ const calcDigest = engine.hmac.digest('hex');
334
+
335
+ let footerHex = '';
336
+ try {
337
+ for (const char of footerStr) {
338
+ const idx = HEADER_ALPHABET.indexOf(char);
339
+ if (idx === -1) throw new Error();
340
+ footerHex += idx.toString(16);
246
341
  }
247
- this.push(output);
342
+ } catch (e) {
343
+ this.emit('error', new Error("Invalid Integrity Seal"));
344
+ return;
345
+ }
346
+
347
+ if (footerHex !== calcDigest) {
348
+ this.emit('error', new Error("FILE_TAMPERED"));
349
+ return;
248
350
  }
249
351
  callback();
250
352
  }
251
353
  });
252
354
  }
355
+
356
+ decodeDataBuf = [];
357
+
358
+ _processDecryptToken(emo, stream) {
359
+ if (this.keywordReverseMap.has(emo)) {
360
+ const currentR = Number(this.rng.next() % BigInt(this.keywordEmojis.length));
361
+ const emoIdx = this.keywordEmojis.indexOf(emo);
362
+
363
+ let baseIdx = (emoIdx - currentR) % this.keywordEmojis.length;
364
+ if (baseIdx < 0) baseIdx += this.keywordEmojis.length;
365
+
366
+ const originalEmo = this.keywordEmojis[baseIdx];
367
+ const keyword = this.keywordReverseMap.get(originalEmo);
368
+
369
+ if (this.decodeDataBuf.length > 0) {
370
+ this.decodeDataBuf = [];
371
+ }
372
+
373
+ stream.push(keyword);
374
+
375
+ } else if (this.dataReverseMap.has(emo)) {
376
+ this.decodeDataBuf.push(this.dataReverseMap.get(emo));
377
+ if (this.decodeDataBuf.length === 4) {
378
+ const chunk = this._decodeBase1024(this.decodeDataBuf);
379
+ this.decodeDataBuf = [];
380
+ const cleanChunk = chunk.filter(b => b !== 0x00);
381
+ stream.push(cleanChunk);
382
+ }
383
+ }
384
+ }
385
+
386
+ _decodeBase1024(indices) {
387
+ let val = 0n;
388
+ for (let i = 3; i >= 0; i--) {
389
+ val = (val * 1024n) + BigInt(indices[i]);
390
+ }
391
+
392
+ const buf = Buffer.alloc(5);
393
+ for (let i = 0; i < 5; i++) {
394
+ buf[i] = Number(val % 256n);
395
+ val = val / 256n;
396
+ }
397
+ return buf;
398
+ }
253
399
  }
package/package.json CHANGED
@@ -1,24 +1,32 @@
1
1
  {
2
2
  "name": "mojic",
3
- "version": "1.0.3",
4
- "description": "Obfuscate C source code into emojis using password-seeded mapping",
3
+ "version": "1.2.4",
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"
12
13
  },
13
14
  "repository": {
14
15
  "type": "git",
15
16
  "url": "git+https://github.com/notamitgamer/mojic.git"
16
17
  },
18
+ "publishConfig": {
19
+ "registry": "https://registry.npmjs.org/",
20
+ "access": "public"
21
+ },
17
22
  "dependencies": {
18
23
  "chalk": "^5.3.0",
19
24
  "commander": "^11.1.0",
20
25
  "inquirer": "^9.2.12"
21
26
  },
27
+ "devDependencies": {
28
+ "pkg": "^5.8.1"
29
+ },
22
30
  "engines": {
23
31
  "node": ">=18.0.0"
24
32
  },
@@ -28,8 +36,22 @@
28
36
  "emoji",
29
37
  "obfuscation",
30
38
  "c",
31
- "security"
39
+ "security",
40
+ "polymorphic"
32
41
  ],
33
42
  "author": "notamitgamer",
34
- "license": "Apache-2.0"
35
- }
43
+ "license": "Apache-2.0",
44
+ "pkg": {
45
+ "scripts": "bin/mojic.js",
46
+ "assets": [
47
+ "lib/**/*",
48
+ "package.json"
49
+ ],
50
+ "targets": [
51
+ "node18-win-x64",
52
+ "node18-linux-x64",
53
+ "node18-macos-x64"
54
+ ],
55
+ "outputPath": "dist"
56
+ }
57
+ }