@nostrbox/cli 1.6.3 ā 1.7.1
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/README.md +122 -38
- package/dist/commands/bip39keys/bip39keys.js +7 -30
- package/dist/commands/bip39keys/bip39keys.js.map +1 -1
- package/dist/commands/bip39zodiac/bip39zodiac.js +743 -214
- package/dist/commands/bip39zodiac/bip39zodiac.js.map +1 -1
- package/dist/utils/cliHelpers.d.ts +36 -0
- package/dist/utils/cliHelpers.d.ts.map +1 -0
- package/dist/utils/cliHelpers.js +112 -0
- package/dist/utils/cliHelpers.js.map +1 -0
- package/package.json +2 -2
|
@@ -39,10 +39,186 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
39
39
|
})();
|
|
40
40
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
41
41
|
const core_1 = require("@nostrbox/core");
|
|
42
|
+
const crypto_1 = require("crypto");
|
|
42
43
|
const argParser_js_1 = require("../../utils/argParser.js");
|
|
44
|
+
const cliHelpers_js_1 = require("../../utils/cliHelpers.js");
|
|
43
45
|
const tabtab = __importStar(require("tabtab"));
|
|
44
46
|
// Valid zodiac signs (converted to lowercase for CLI compatibility)
|
|
45
47
|
const VALID_ZODIAC_SIGNS = core_1.ZODIAC_SIGNS.map((sign) => sign.toLowerCase());
|
|
48
|
+
/**
|
|
49
|
+
* Derives encryption password from user-memorable parameters
|
|
50
|
+
*
|
|
51
|
+
* @param color - Color selection (blue, green, purple, etc.)
|
|
52
|
+
* @param birthday - Birthday day number (1-31)
|
|
53
|
+
* @param zodiacSign - Zodiac sign (Aries, Taurus, etc.)
|
|
54
|
+
* @param operation - Operation type (add or subtract)
|
|
55
|
+
* @returns Derived password string with format:
|
|
56
|
+
* - ADD: UPPERCASE_COLOR + "+" + birthday + "+" + lowercase_zodiac (e.g., "BLUE+17+aries")
|
|
57
|
+
* - SUBTRACT: lowercase_color + "-" + birthday + "-" + UPPERCASE_ZODIAC (e.g., "blue-17-ARIES")
|
|
58
|
+
*/
|
|
59
|
+
function deriveMemoryKeys(color, birthday, zodiacSign, operation) {
|
|
60
|
+
if (operation === 'add') {
|
|
61
|
+
return `${color.toUpperCase()}+${birthday}+${zodiacSign.toLowerCase()}`;
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
return `${color.toLowerCase()}-${birthday}-${zodiacSign.toUpperCase()}`;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Generates random Base64 string of specified length
|
|
69
|
+
*/
|
|
70
|
+
function generateRandomBase64(length) {
|
|
71
|
+
const bytesNeeded = Math.ceil((length * 3) / 4);
|
|
72
|
+
const randomBytesBuffer = (0, crypto_1.randomBytes)(bytesNeeded);
|
|
73
|
+
const base64String = randomBytesBuffer.toString('base64');
|
|
74
|
+
return base64String.substring(0, length);
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Splits an encrypted zodiac password into two parts based on birthday and color
|
|
78
|
+
*
|
|
79
|
+
* @param encryptedPassword - The encrypted zodiac password
|
|
80
|
+
* @param birthday - Birthday value (1-31)
|
|
81
|
+
* @param colorValue - Color value (1-7)
|
|
82
|
+
* @returns Two parts of same length as original, each containing partial original data
|
|
83
|
+
*/
|
|
84
|
+
function splitZodiacPassword(encryptedPassword, birthday, colorValue) {
|
|
85
|
+
const length = encryptedPassword.length;
|
|
86
|
+
const splitPoint = birthday + colorValue;
|
|
87
|
+
if (splitPoint >= length) {
|
|
88
|
+
throw new Error(`Split point (${splitPoint}) must be less than password length (${length})`);
|
|
89
|
+
}
|
|
90
|
+
// Part 1: First splitPoint chars from original + random garbage for rest
|
|
91
|
+
const part1Original = encryptedPassword.substring(0, splitPoint);
|
|
92
|
+
const part1Random = generateRandomBase64(length - splitPoint);
|
|
93
|
+
const part1 = part1Original + part1Random;
|
|
94
|
+
// Part 2: Random garbage for first splitPoint chars + remaining original chars
|
|
95
|
+
const part2Random = generateRandomBase64(splitPoint);
|
|
96
|
+
const part2Original = encryptedPassword.substring(splitPoint);
|
|
97
|
+
const part2 = part2Random + part2Original;
|
|
98
|
+
return {
|
|
99
|
+
part1,
|
|
100
|
+
part2,
|
|
101
|
+
splitPoint
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Combines two split parts to recover the original encrypted zodiac password
|
|
106
|
+
*
|
|
107
|
+
* @param part1 - First split part
|
|
108
|
+
* @param part2 - Second split part
|
|
109
|
+
* @param birthday - Birthday value (1-31)
|
|
110
|
+
* @param colorValue - Color value (1-7)
|
|
111
|
+
* @returns Original encrypted zodiac password
|
|
112
|
+
*/
|
|
113
|
+
function combineZodiacPassword(part1, part2, birthday, colorValue) {
|
|
114
|
+
const splitPoint = birthday + colorValue;
|
|
115
|
+
if (part1.length !== part2.length) {
|
|
116
|
+
throw new Error(`Split parts must have same length (part1: ${part1.length}, part2: ${part2.length})`);
|
|
117
|
+
}
|
|
118
|
+
// Extract first splitPoint chars from part1
|
|
119
|
+
const firstPart = part1.substring(0, splitPoint);
|
|
120
|
+
// Extract remaining chars from part2
|
|
121
|
+
const secondPart = part2.substring(splitPoint);
|
|
122
|
+
return firstPart + secondPart;
|
|
123
|
+
}
|
|
124
|
+
// Color values mapping
|
|
125
|
+
const COLOR_VALUES = {
|
|
126
|
+
blue: 1,
|
|
127
|
+
green: 2,
|
|
128
|
+
purple: 3,
|
|
129
|
+
orange: 4,
|
|
130
|
+
yellow: 5,
|
|
131
|
+
pink: 6,
|
|
132
|
+
brown: 7
|
|
133
|
+
};
|
|
134
|
+
const VALID_COLORS = Object.keys(COLOR_VALUES);
|
|
135
|
+
/**
|
|
136
|
+
* Generates export JSON with encrypted split parts and all chain addresses
|
|
137
|
+
* Derives public addresses from the seed phrase for Bitcoin, Ethereum, Solana, TRON, and Nostr
|
|
138
|
+
*/
|
|
139
|
+
function generateExportData(seedPhrase, splitPart1, splitPart2, passphrase = '') {
|
|
140
|
+
const mnemonic = seedPhrase.join(' ');
|
|
141
|
+
// Derive Bitcoin native (bc1q...) address
|
|
142
|
+
const bitcoinResult = (0, core_1.deriveKeyFromMnemonic)({
|
|
143
|
+
mnemonic,
|
|
144
|
+
chain: 'bitcoin',
|
|
145
|
+
format: 'native',
|
|
146
|
+
passphrase,
|
|
147
|
+
accountIndex: 0,
|
|
148
|
+
changeIndex: 0,
|
|
149
|
+
addressIndex: 0
|
|
150
|
+
});
|
|
151
|
+
// Derive Nostr public key (npub1...)
|
|
152
|
+
const nostrResult = (0, core_1.deriveKeyFromMnemonic)({
|
|
153
|
+
mnemonic,
|
|
154
|
+
chain: 'nostr',
|
|
155
|
+
format: 'bech32',
|
|
156
|
+
passphrase,
|
|
157
|
+
accountIndex: 0,
|
|
158
|
+
changeIndex: 0,
|
|
159
|
+
addressIndex: 0
|
|
160
|
+
});
|
|
161
|
+
// Derive Ethereum address (0x...)
|
|
162
|
+
const ethereumResult = (0, core_1.deriveKeyFromMnemonic)({
|
|
163
|
+
mnemonic,
|
|
164
|
+
chain: 'ethereum',
|
|
165
|
+
format: 'native',
|
|
166
|
+
passphrase,
|
|
167
|
+
accountIndex: 0,
|
|
168
|
+
changeIndex: 0,
|
|
169
|
+
addressIndex: 0
|
|
170
|
+
});
|
|
171
|
+
// Derive Solana address
|
|
172
|
+
const solanaResult = (0, core_1.deriveKeyFromMnemonic)({
|
|
173
|
+
mnemonic,
|
|
174
|
+
chain: 'solana',
|
|
175
|
+
format: 'native',
|
|
176
|
+
passphrase,
|
|
177
|
+
accountIndex: 0
|
|
178
|
+
});
|
|
179
|
+
// Derive TRON address (T...)
|
|
180
|
+
const tronResult = (0, core_1.deriveKeyFromMnemonic)({
|
|
181
|
+
mnemonic,
|
|
182
|
+
chain: 'tron',
|
|
183
|
+
format: 'native',
|
|
184
|
+
passphrase,
|
|
185
|
+
accountIndex: 0,
|
|
186
|
+
changeIndex: 0,
|
|
187
|
+
addressIndex: 0
|
|
188
|
+
});
|
|
189
|
+
// Validate that all addresses were derived successfully
|
|
190
|
+
if (!bitcoinResult.address) {
|
|
191
|
+
throw new Error('Failed to derive Bitcoin address');
|
|
192
|
+
}
|
|
193
|
+
if (!nostrResult.publicKey) {
|
|
194
|
+
throw new Error('Failed to derive Nostr public key');
|
|
195
|
+
}
|
|
196
|
+
if (!ethereumResult.address) {
|
|
197
|
+
throw new Error('Failed to derive Ethereum address');
|
|
198
|
+
}
|
|
199
|
+
if (!solanaResult.address) {
|
|
200
|
+
throw new Error('Failed to derive Solana address');
|
|
201
|
+
}
|
|
202
|
+
if (!tronResult.address) {
|
|
203
|
+
throw new Error('Failed to derive TRON address');
|
|
204
|
+
}
|
|
205
|
+
return {
|
|
206
|
+
encZodiacPwdSplit1: splitPart1,
|
|
207
|
+
encZodiacPwdSplit2: splitPart2,
|
|
208
|
+
nostr: nostrResult.publicKey,
|
|
209
|
+
bitcoin: bitcoinResult.address,
|
|
210
|
+
ethereum: ethereumResult.address,
|
|
211
|
+
solana: solanaResult.address,
|
|
212
|
+
tron: tronResult.address
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
// Constants for format detection and validation
|
|
216
|
+
const BASE58_PATTERN = /^[123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz]+$/;
|
|
217
|
+
const MAX_ZODIAC_PASSWORD_LENGTH = 30;
|
|
218
|
+
/**
|
|
219
|
+
* Creates and configures the argument parser for the CLI
|
|
220
|
+
* Defines all available flags, options, and their validation rules
|
|
221
|
+
*/
|
|
46
222
|
function createParser() {
|
|
47
223
|
return new argParser_js_1.ArgumentParser({
|
|
48
224
|
positional: {
|
|
@@ -53,14 +229,11 @@ function createParser() {
|
|
|
53
229
|
options: [
|
|
54
230
|
// Core mandatory flags
|
|
55
231
|
{
|
|
56
|
-
name: '
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
name: 'black',
|
|
62
|
-
type: 'flag',
|
|
63
|
-
description: 'Black: subtract birthday offset (-) (mandatory: choose --red or --black)'
|
|
232
|
+
name: 'operation',
|
|
233
|
+
alias: 'o',
|
|
234
|
+
type: 'string',
|
|
235
|
+
description: 'Operation type: add or subtract (mandatory)',
|
|
236
|
+
choices: ['add', 'subtract']
|
|
64
237
|
},
|
|
65
238
|
{
|
|
66
239
|
name: 'birthday',
|
|
@@ -75,6 +248,13 @@ function createParser() {
|
|
|
75
248
|
description: 'Zodiac sign for shuffling (mandatory)',
|
|
76
249
|
choices: [...VALID_ZODIAC_SIGNS]
|
|
77
250
|
},
|
|
251
|
+
{
|
|
252
|
+
name: 'color',
|
|
253
|
+
alias: 'c',
|
|
254
|
+
type: 'string',
|
|
255
|
+
description: 'Color for additional offset: blue(1), green(2), purple(3), orange(4), yellow(5), pink(6), brown(7) (mandatory)',
|
|
256
|
+
choices: [...VALID_COLORS]
|
|
257
|
+
},
|
|
78
258
|
// Main use cases
|
|
79
259
|
{
|
|
80
260
|
name: 'restore',
|
|
@@ -94,15 +274,47 @@ function createParser() {
|
|
|
94
274
|
},
|
|
95
275
|
{
|
|
96
276
|
name: 'load',
|
|
277
|
+
type: 'flag',
|
|
278
|
+
description: 'Modifier flag: when used, data input flags expect file paths instead of direct values'
|
|
279
|
+
},
|
|
280
|
+
// Old import methods (for backward compatibility)
|
|
281
|
+
{
|
|
282
|
+
name: 'codes',
|
|
283
|
+
type: 'string',
|
|
284
|
+
description: 'Secret codes: provide numbers directly, or file path if --load is used'
|
|
285
|
+
},
|
|
286
|
+
{
|
|
287
|
+
name: 'zodiacPwd',
|
|
288
|
+
type: 'string',
|
|
289
|
+
description: 'Plain zodiac password: provide Base58 directly, or file path if --load is used'
|
|
290
|
+
},
|
|
291
|
+
{
|
|
292
|
+
name: 'encZodiacPwd',
|
|
97
293
|
type: 'string',
|
|
98
|
-
description: '
|
|
294
|
+
description: 'Encrypted zodiac password: provide string directly, or file path if --load is used'
|
|
295
|
+
},
|
|
296
|
+
{
|
|
297
|
+
name: 'encZodiacPwdSplit',
|
|
298
|
+
type: 'string',
|
|
299
|
+
description: 'Encrypted split parts: provide comma-separated directly, or file path if --load is used'
|
|
300
|
+
},
|
|
301
|
+
// Encryption options
|
|
302
|
+
{
|
|
303
|
+
name: 'passphrase',
|
|
304
|
+
type: 'string',
|
|
305
|
+
description: 'Custom user password for encryption/decryption (overrides auto-derived memory keys)'
|
|
99
306
|
},
|
|
100
307
|
// Output format options
|
|
101
308
|
{
|
|
102
309
|
name: 'zodiac-password',
|
|
103
310
|
alias: 'p',
|
|
104
311
|
type: 'flag',
|
|
105
|
-
description: 'Output/input zodiac password (Base58) instead of 12 numbers'
|
|
312
|
+
description: 'Output/input zodiac password (Base58, always encrypted and split) instead of 12 numbers'
|
|
313
|
+
},
|
|
314
|
+
{
|
|
315
|
+
name: 'export',
|
|
316
|
+
type: 'string',
|
|
317
|
+
description: 'Export JSON with encrypted split parts and public addresses to specified file'
|
|
106
318
|
},
|
|
107
319
|
// File options
|
|
108
320
|
{
|
|
@@ -114,18 +326,24 @@ function createParser() {
|
|
|
114
326
|
]
|
|
115
327
|
});
|
|
116
328
|
}
|
|
329
|
+
/**
|
|
330
|
+
* Sets up shell tab completion for the bip39zodiac command
|
|
331
|
+
* Provides intelligent autocomplete for flags, operations, zodiac signs, and colors
|
|
332
|
+
*/
|
|
117
333
|
async function setupCompletion(env) {
|
|
118
334
|
// Handle completion request - return available options
|
|
119
335
|
const flagCompletions = [
|
|
120
|
-
'--
|
|
121
|
-
'--black',
|
|
336
|
+
'--operation',
|
|
122
337
|
'--birthday',
|
|
123
338
|
'--zodiac',
|
|
339
|
+
'--color',
|
|
124
340
|
'--new',
|
|
125
341
|
'--seed',
|
|
126
342
|
'--restore',
|
|
127
343
|
'--load',
|
|
344
|
+
'--passphrase',
|
|
128
345
|
'--zodiac-password',
|
|
346
|
+
'--export',
|
|
129
347
|
'--save'
|
|
130
348
|
];
|
|
131
349
|
const commandCompletions = ['install', 'uninstall'];
|
|
@@ -139,74 +357,84 @@ async function setupCompletion(env) {
|
|
|
139
357
|
return tabtab.log(matches);
|
|
140
358
|
}
|
|
141
359
|
}
|
|
360
|
+
// Special case for operation values
|
|
361
|
+
const operationMatches = (0, cliHelpers_js_1.handleOptionValueCompletion)(env.line || '', current, 'operation', ['add', 'subtract']);
|
|
362
|
+
if (operationMatches) {
|
|
363
|
+
return tabtab.log(operationMatches);
|
|
364
|
+
}
|
|
142
365
|
// Special case for zodiac values
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
366
|
+
const zodiacMatches = (0, cliHelpers_js_1.handleOptionValueCompletion)(env.line || '', current, 'zodiac', VALID_ZODIAC_SIGNS);
|
|
367
|
+
if (zodiacMatches) {
|
|
368
|
+
return tabtab.log(zodiacMatches);
|
|
369
|
+
}
|
|
370
|
+
// Special case for color values
|
|
371
|
+
const colorMatches = (0, cliHelpers_js_1.handleOptionValueCompletion)(env.line || '', current, 'color', VALID_COLORS);
|
|
372
|
+
if (colorMatches) {
|
|
373
|
+
return tabtab.log(colorMatches);
|
|
147
374
|
}
|
|
148
375
|
// Filter flag names based on current input
|
|
149
376
|
const matches = flagCompletions.filter((comp) => comp.startsWith(current));
|
|
150
377
|
return tabtab.log(matches);
|
|
151
378
|
}
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
name: 'bip39zodiac',
|
|
156
|
-
completer: 'bip39zodiac'
|
|
157
|
-
});
|
|
158
|
-
console.log('ā
Shell completion installed successfully!');
|
|
159
|
-
console.log('š You may need to restart your shell or run: source ~/.bashrc (or equivalent)');
|
|
160
|
-
}
|
|
161
|
-
catch (error) {
|
|
162
|
-
console.error('ā Failed to install completion:', error instanceof Error ? error.message : String(error));
|
|
163
|
-
process.exit(1);
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
async function uninstallCompletion() {
|
|
167
|
-
try {
|
|
168
|
-
await tabtab.uninstall({
|
|
169
|
-
name: 'bip39zodiac'
|
|
170
|
-
});
|
|
171
|
-
console.log('ā
Shell completion uninstalled successfully!');
|
|
172
|
-
}
|
|
173
|
-
catch (error) {
|
|
174
|
-
console.error('ā Failed to uninstall completion:', error instanceof Error ? error.message : String(error));
|
|
175
|
-
process.exit(1);
|
|
176
|
-
}
|
|
177
|
-
}
|
|
379
|
+
// Create shared completion installer and uninstaller using utility functions
|
|
380
|
+
const installCompletion = (0, cliHelpers_js_1.createCompletionInstaller)('bip39zodiac');
|
|
381
|
+
const uninstallCompletion = (0, cliHelpers_js_1.createCompletionUninstaller)('bip39zodiac');
|
|
178
382
|
function showHelp() {
|
|
179
383
|
console.log(`
|
|
180
384
|
š Seed Phrase CLI Tool - BIP39 Zodiac Operations
|
|
181
385
|
|
|
182
386
|
USAGE:
|
|
183
|
-
|
|
387
|
+
# Generation
|
|
388
|
+
bip39zodiac [GENERATION_INPUT] [CONVERSION_FLAGS] [OPTIONS]
|
|
389
|
+
|
|
390
|
+
# Restoration
|
|
391
|
+
bip39zodiac [RESTORE_INPUT] --restore [CONVERSION_FLAGS] [OPTIONS]
|
|
392
|
+
|
|
393
|
+
# Commands
|
|
184
394
|
bip39zodiac <install|uninstall>
|
|
185
395
|
|
|
186
|
-
INPUT METHODS (choose one):
|
|
396
|
+
GENERATION INPUT METHODS (choose one):
|
|
187
397
|
--new, -n Generate new random 12-word seed phrase
|
|
188
|
-
--seed "
|
|
189
|
-
--
|
|
398
|
+
--seed "12 words" Transform existing seed phrase (12 words directly)
|
|
399
|
+
--seed <file> ... or file path when used with --load flag
|
|
190
400
|
|
|
191
|
-
|
|
192
|
-
--
|
|
193
|
-
--
|
|
194
|
-
--
|
|
401
|
+
RESTORE INPUT METHODS (choose one, use with --restore):
|
|
402
|
+
--codes "numbers" Secret codes: provide numbers directly
|
|
403
|
+
--codes <file> ... or file path when used with --load flag
|
|
404
|
+
--zodiacPwd "base58" Plain zodiac password: provide Base58 directly
|
|
405
|
+
--zodiacPwd <file> ... or file path when used with --load flag
|
|
406
|
+
--encZodiacPwd "encrypted" Encrypted password: provide string directly
|
|
407
|
+
--encZodiacPwd <file> ... or file path when used with --load flag
|
|
408
|
+
--encZodiacPwdSplit "p1,p2" Split parts: provide comma-separated directly
|
|
409
|
+
--encZodiacPwdSplit <file> ... or file path when used with --load flag (2 lines or 1 line with comma)
|
|
410
|
+
--load Modifier flag: makes above flags expect file paths
|
|
195
411
|
|
|
196
412
|
CONVERSION FLAGS (all required):
|
|
197
|
-
--
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
--birthday <1-31>
|
|
202
|
-
--zodiac <sign>
|
|
413
|
+
--operation, -o <add|subtract> Operation type (mandatory):
|
|
414
|
+
add Add offsets (+)
|
|
415
|
+
subtract Subtract offsets (-)
|
|
416
|
+
|
|
417
|
+
--birthday, -b <1-31> Birthday day number (mandatory)
|
|
418
|
+
--zodiac, -z <sign> Zodiac sign for shuffling (mandatory)
|
|
419
|
+
--color, -c <color> Color for additional offset (mandatory):
|
|
420
|
+
blue(1), green(2), purple(3), orange(4), yellow(5), pink(6), brown(7)
|
|
421
|
+
|
|
422
|
+
ENCRYPTION OPTIONS:
|
|
423
|
+
--passphrase <password> Custom user password for encryption/decryption
|
|
424
|
+
(overrides auto-derived memory keys)
|
|
425
|
+
Requirements: min 8 chars, letters + numbers + special char,
|
|
426
|
+
no common weak passwords (e.g., "password", "123456")
|
|
427
|
+
|
|
428
|
+
OUTPUT OPTIONS:
|
|
429
|
+
--zodiac-password, -p Output zodiac password (Base58) format
|
|
430
|
+
--export <file> Export JSON with encrypted split parts and public addresses
|
|
431
|
+
to specified file (Bitcoin, Ethereum, Solana, TRON, Nostr)
|
|
432
|
+
--save <file> Save results to file
|
|
203
433
|
|
|
204
434
|
COMMANDS:
|
|
205
435
|
install Install shell completion for this command
|
|
206
436
|
uninstall Uninstall shell completion for this command
|
|
207
437
|
|
|
208
|
-
OPTIONS:
|
|
209
|
-
|
|
210
438
|
ZODIAC SIGNS & ALGORITHMS:
|
|
211
439
|
Original: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11] - Original positions
|
|
212
440
|
|
|
@@ -223,43 +451,66 @@ ZODIAC SIGNS & ALGORITHMS:
|
|
|
223
451
|
aquarius: [3, 4, 5, 0, 1, 2, 9, 10, 11, 6, 7, 8] - Swap segment pairs
|
|
224
452
|
pisces: [6, 7, 8, 9, 10, 11, 5, 4, 3, 2, 1, 0] - Second half + mirrored first
|
|
225
453
|
|
|
226
|
-
ā ļø WARNING: Use a family member's birthday, a
|
|
454
|
+
ā ļø WARNING: Use a family member's birthday, a celebrity zodiac, or another memorable combination.
|
|
227
455
|
Not your own birthday and zodiac for maximum security.
|
|
228
456
|
|
|
229
457
|
EXAMPLES:
|
|
458
|
+
# GENERATION
|
|
230
459
|
# Generate new seed phrase and transform it
|
|
231
|
-
bip39zodiac --new --
|
|
232
|
-
|
|
233
|
-
# Transform existing seed phrase
|
|
234
|
-
bip39zodiac --seed "abandon ability able..." --
|
|
235
|
-
|
|
236
|
-
#
|
|
237
|
-
bip39zodiac --seed
|
|
238
|
-
|
|
239
|
-
#
|
|
240
|
-
bip39zodiac --
|
|
241
|
-
|
|
242
|
-
#
|
|
243
|
-
bip39zodiac --
|
|
244
|
-
|
|
245
|
-
#
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
bip39zodiac --
|
|
460
|
+
bip39zodiac --new --operation=add --birthday=17 --zodiac=aries --color=blue
|
|
461
|
+
|
|
462
|
+
# Transform existing seed phrase and save
|
|
463
|
+
bip39zodiac --seed "abandon ability able..." --save codes.txt --operation=subtract --birthday=25 --zodiac=leo --color=green
|
|
464
|
+
|
|
465
|
+
# Load seed phrase from file and save with zodiac password format
|
|
466
|
+
bip39zodiac --load --seed seedphrase.txt --zodiac-password --save password.txt --operation=add --birthday=10 --zodiac=virgo --color=orange
|
|
467
|
+
|
|
468
|
+
# Generate with custom passphrase instead of memory keys
|
|
469
|
+
bip39zodiac --new --passphrase "MySecretPassword.123" --operation=add --birthday=17 --zodiac=aries --color=blue
|
|
470
|
+
|
|
471
|
+
# Export JSON with encrypted split parts and all chain addresses to file
|
|
472
|
+
bip39zodiac --new --export export.json --operation=add --birthday=17 --zodiac=aries --color=blue
|
|
473
|
+
|
|
474
|
+
# RESTORATION
|
|
475
|
+
# Restore from secret codes
|
|
476
|
+
bip39zodiac --restore --codes "22 23 24 25 26 27 28 29 30 19 20 21" --operation=add --birthday=17 --zodiac=aries --color=blue
|
|
477
|
+
|
|
478
|
+
# Restore from secret codes loaded from file
|
|
479
|
+
bip39zodiac --restore --load --codes codes.txt --operation=add --birthday=17 --zodiac=aries --color=blue
|
|
480
|
+
|
|
481
|
+
# Restore from plain zodiac password
|
|
482
|
+
bip39zodiac --restore --zodiacPwd "1P1Q1R1S1T1U1V1W1X1L1M1N" --operation=subtract --birthday=25 --zodiac=leo --color=yellow
|
|
483
|
+
|
|
484
|
+
# Restore from encrypted zodiac password (single string)
|
|
485
|
+
bip39zodiac --restore --encZodiacPwd "s2ubbiTDzq..." --operation=add --birthday=10 --zodiac=virgo --color=pink
|
|
486
|
+
|
|
487
|
+
# Restore from encrypted split parts
|
|
488
|
+
bip39zodiac --restore --encZodiacPwdSplit "part1...,part2..." --operation=add --birthday=15 --zodiac=gemini --color=brown
|
|
489
|
+
|
|
490
|
+
# Load codes from file and restore (--load makes --codes expect file path)
|
|
491
|
+
bip39zodiac --restore --load --codes codes.txt --operation=add --birthday=17 --zodiac=aries --color=blue
|
|
492
|
+
|
|
493
|
+
# Load encrypted split from file (2 lines or 1 line with comma)
|
|
494
|
+
bip39zodiac --restore --load --encZodiacPwdSplit password.txt --operation=add --birthday=10 --zodiac=virgo --color=orange
|
|
495
|
+
|
|
496
|
+
# Restore with custom passphrase
|
|
497
|
+
bip39zodiac --restore --encZodiacPwdSplit "part1...,part2..." --passphrase "MySecretPassword.123" --operation=add --birthday=15 --zodiac=gemini --color=brown
|
|
498
|
+
|
|
499
|
+
# INSTALL COMPLETION
|
|
500
|
+
bip39zodiac install
|
|
254
501
|
`);
|
|
255
502
|
}
|
|
503
|
+
/**
|
|
504
|
+
* Validates that all core mandatory flags are present and have valid values
|
|
505
|
+
* Throws descriptive errors if any validation fails
|
|
506
|
+
*/
|
|
256
507
|
function validateCoreFlags(args) {
|
|
257
|
-
// Validate
|
|
258
|
-
if (!args.
|
|
259
|
-
throw new Error('Core flag required:
|
|
508
|
+
// Validate operation
|
|
509
|
+
if (!args.operation) {
|
|
510
|
+
throw new Error('Core flag required: --operation <add|subtract> must be specified');
|
|
260
511
|
}
|
|
261
|
-
if (args.
|
|
262
|
-
throw new Error('
|
|
512
|
+
if (args.operation !== 'add' && args.operation !== 'subtract') {
|
|
513
|
+
throw new Error('Operation must be either "add" or "subtract"');
|
|
263
514
|
}
|
|
264
515
|
// Validate birthday
|
|
265
516
|
const birthdayArg = args.birthday;
|
|
@@ -274,125 +525,207 @@ function validateCoreFlags(args) {
|
|
|
274
525
|
if (!args.zodiac) {
|
|
275
526
|
throw new Error('Core flag required: --zodiac <sign> must be specified');
|
|
276
527
|
}
|
|
528
|
+
// Validate color
|
|
529
|
+
if (!args.color) {
|
|
530
|
+
throw new Error('Core flag required: --color <blue|green|purple|orange|yellow|pink|brown> must be specified');
|
|
531
|
+
}
|
|
532
|
+
if (!VALID_COLORS.includes(args.color)) {
|
|
533
|
+
throw new Error(`Color must be one of: ${VALID_COLORS.join(', ')}`);
|
|
534
|
+
}
|
|
277
535
|
}
|
|
536
|
+
/**
|
|
537
|
+
* Validates that exactly one input method is specified and required combinations are met
|
|
538
|
+
* For generation: use --new or --seed (seed phrase)
|
|
539
|
+
* For restore: use --codes, --zodiacPwd, --encZodiacPwd, or --encZodiacPwdSplit (optionally with --load)
|
|
540
|
+
*/
|
|
278
541
|
function validateUseCase(args) {
|
|
279
|
-
//
|
|
280
|
-
if (args.restore
|
|
281
|
-
|
|
542
|
+
// For restore mode
|
|
543
|
+
if (args.restore) {
|
|
544
|
+
// --seed cannot be used with --restore
|
|
545
|
+
if (args.seed) {
|
|
546
|
+
throw new Error('--seed is only for seed phrases during generation. For restore, use --codes, --zodiacPwd, --encZodiacPwd, or --encZodiacPwdSplit');
|
|
547
|
+
}
|
|
548
|
+
if (args.new) {
|
|
549
|
+
throw new Error('--new cannot be used with --restore');
|
|
550
|
+
}
|
|
551
|
+
// Must have one of the restore input methods
|
|
552
|
+
if (!args.codes &&
|
|
553
|
+
!args.zodiacPwd &&
|
|
554
|
+
!args.encZodiacPwd &&
|
|
555
|
+
!args.encZodiacPwdSplit) {
|
|
556
|
+
throw new Error('--restore requires one of: --codes, --zodiacPwd, --encZodiacPwd, or --encZodiacPwdSplit (optionally with --load <file>)');
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
else {
|
|
560
|
+
// For generation mode, cannot use restore-specific flags
|
|
561
|
+
if (args.codes ||
|
|
562
|
+
args.zodiacPwd ||
|
|
563
|
+
args.encZodiacPwd ||
|
|
564
|
+
args.encZodiacPwdSplit) {
|
|
565
|
+
throw new Error('Restore input methods (--codes, --zodiacPwd, --encZodiacPwd, --encZodiacPwdSplit) require --restore flag');
|
|
566
|
+
}
|
|
282
567
|
}
|
|
283
568
|
// Check that we have at least one input method
|
|
284
|
-
const inputs = [
|
|
569
|
+
const inputs = [
|
|
570
|
+
args.new,
|
|
571
|
+
args.seed,
|
|
572
|
+
args.codes,
|
|
573
|
+
args.zodiacPwd,
|
|
574
|
+
args.encZodiacPwd,
|
|
575
|
+
args.encZodiacPwdSplit
|
|
576
|
+
].filter(Boolean);
|
|
285
577
|
if (inputs.length === 0) {
|
|
286
|
-
throw new Error('Input required:
|
|
578
|
+
throw new Error('Input required: for generation use --new or --seed; for restore use --codes, --zodiacPwd, --encZodiacPwd, or --encZodiacPwdSplit');
|
|
287
579
|
}
|
|
580
|
+
// Only one data input method allowed (--load is separate as it's just the file path)
|
|
288
581
|
if (inputs.length > 1) {
|
|
289
|
-
throw new Error('Input conflict: only one
|
|
582
|
+
throw new Error('Input conflict: only one data input method can be used at a time');
|
|
290
583
|
}
|
|
291
584
|
}
|
|
585
|
+
/**
|
|
586
|
+
* Gets seed phrase based on input method (new, seed, or load)
|
|
587
|
+
* Returns empty array for restore mode (handled separately in main)
|
|
588
|
+
*/
|
|
292
589
|
function getSeedPhrase(args) {
|
|
590
|
+
// Handle restore mode upfront - will be processed separately in main
|
|
591
|
+
if (args.restore) {
|
|
592
|
+
return [];
|
|
593
|
+
}
|
|
293
594
|
if (args.new) {
|
|
294
595
|
console.log('š² Generating 12-word seed phrase...');
|
|
295
596
|
return (0, core_1.generateSecureSeedPhrase)(12, 128);
|
|
296
597
|
}
|
|
297
598
|
if (args.seed) {
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
.
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
}
|
|
310
|
-
if (args.load) {
|
|
311
|
-
console.log(`š Loading from file: ${args.load}`);
|
|
312
|
-
try {
|
|
313
|
-
const fileContent = (0, core_1.loadArrayFromFile)(args.load);
|
|
314
|
-
// fileContent is always string[], join and split to handle both formats
|
|
315
|
-
const content = fileContent
|
|
316
|
-
.join(' ')
|
|
317
|
-
.split(/\s+/)
|
|
318
|
-
.filter((item) => item.length > 0);
|
|
319
|
-
if (args.restore) {
|
|
320
|
-
// For restore case - return empty array, will be handled in main function
|
|
321
|
-
console.log('š„ Loading numbers for restoration');
|
|
322
|
-
return [];
|
|
323
|
-
}
|
|
324
|
-
else {
|
|
325
|
-
// For seed phrase transformation
|
|
326
|
-
console.log('š„ Loading seed phrase for transformation');
|
|
599
|
+
if (args.load) {
|
|
600
|
+
// When --load is present, args.seed contains the file path
|
|
601
|
+
console.log(`š Loading seed phrase from file: ${args.seed}`);
|
|
602
|
+
try {
|
|
603
|
+
const fileContent = (0, core_1.loadArrayFromFile)(args.seed);
|
|
604
|
+
// fileContent is always string[], join and split to handle both formats
|
|
605
|
+
const content = fileContent
|
|
606
|
+
.join(' ')
|
|
607
|
+
.split(/\s+/)
|
|
608
|
+
.filter((item) => item.length > 0);
|
|
609
|
+
console.log('š„ Processing seed phrase for transformation');
|
|
327
610
|
(0, core_1.validateBip39Words)(content);
|
|
328
611
|
return content;
|
|
329
612
|
}
|
|
613
|
+
catch (error) {
|
|
614
|
+
throw new Error(`Failed to load file '${args.seed}': ${error instanceof Error ? error.message : String(error)}`);
|
|
615
|
+
}
|
|
330
616
|
}
|
|
331
|
-
|
|
332
|
-
|
|
617
|
+
else {
|
|
618
|
+
// Direct input as string
|
|
619
|
+
console.log('š„ Processing existing seed phrase...');
|
|
620
|
+
const words = args.seed
|
|
621
|
+
.split(' ')
|
|
622
|
+
.map((word) => word.trim())
|
|
623
|
+
.filter((word) => word.length > 0);
|
|
624
|
+
(0, core_1.validateBip39Words)(words);
|
|
625
|
+
return words;
|
|
333
626
|
}
|
|
334
627
|
}
|
|
335
628
|
throw new Error('No valid use case specified');
|
|
336
629
|
}
|
|
630
|
+
/**
|
|
631
|
+
* Formats source information for error messages
|
|
632
|
+
*/
|
|
633
|
+
function formatSource(source) {
|
|
634
|
+
return source ? ` from ${source}` : '';
|
|
635
|
+
}
|
|
636
|
+
/**
|
|
637
|
+
* Gets appropriate label for input source
|
|
638
|
+
*/
|
|
639
|
+
function getInputLabel(source) {
|
|
640
|
+
return source ? 'File content' : 'Input';
|
|
641
|
+
}
|
|
642
|
+
/**
|
|
643
|
+
* Detects whether input appears to be a zodiac password or numbers
|
|
644
|
+
*/
|
|
645
|
+
function detectInputFormat(input) {
|
|
646
|
+
const hasSpaces = input.includes(' ');
|
|
647
|
+
const hasOnlyBase58Chars = BASE58_PATTERN.test(input);
|
|
648
|
+
return !hasSpaces &&
|
|
649
|
+
hasOnlyBase58Chars &&
|
|
650
|
+
input.length < MAX_ZODIAC_PASSWORD_LENGTH
|
|
651
|
+
? 'zodiac-password'
|
|
652
|
+
: 'numbers';
|
|
653
|
+
}
|
|
654
|
+
/**
|
|
655
|
+
* Parses space-separated numbers from input string
|
|
656
|
+
*/
|
|
657
|
+
function parseNumbers(input) {
|
|
658
|
+
return input
|
|
659
|
+
.split(/\s+/)
|
|
660
|
+
.filter((item) => item.length > 0)
|
|
661
|
+
.map((code) => parseInt(code.trim()))
|
|
662
|
+
.filter((code) => !isNaN(code));
|
|
663
|
+
}
|
|
664
|
+
/**
|
|
665
|
+
* Decodes a zodiac password with explicit flag
|
|
666
|
+
*/
|
|
667
|
+
function decodeZodiacPassword(input, source) {
|
|
668
|
+
console.log(`š„ Decoding zodiac password${formatSource(source)}...`);
|
|
669
|
+
try {
|
|
670
|
+
const secretCodes = (0, core_1.decodePassword)(input);
|
|
671
|
+
console.log(`š Zodiac password decoded to: ${secretCodes.join(' ')}`);
|
|
672
|
+
return secretCodes;
|
|
673
|
+
}
|
|
674
|
+
catch (error) {
|
|
675
|
+
throw new Error(`Failed to decode zodiac password${formatSource(source)}: ${error instanceof Error ? error.message : String(error)}`);
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
/**
|
|
679
|
+
* Attempts to auto-detect and decode zodiac password format
|
|
680
|
+
*/
|
|
681
|
+
function decodeZodiacPasswordWithAutoDetect(input, source) {
|
|
682
|
+
console.log(`š Auto-detecting zodiac password format${formatSource(source)}...`);
|
|
683
|
+
try {
|
|
684
|
+
const secretCodes = (0, core_1.decodePassword)(input);
|
|
685
|
+
console.log(`š Zodiac password decoded to: ${secretCodes.join(' ')}`);
|
|
686
|
+
return secretCodes;
|
|
687
|
+
}
|
|
688
|
+
catch (error) {
|
|
689
|
+
throw new Error(`${getInputLabel(source)} appears to be zodiac password but failed to decode: ${error instanceof Error ? error.message : String(error)}. Use --zodiac-password flag or ${source ? 'ensure file contains numbers' : 'provide numbers instead'}.`);
|
|
690
|
+
}
|
|
691
|
+
}
|
|
337
692
|
/**
|
|
338
693
|
* Parses input string as either zodiac password or numbers based on flags and content
|
|
339
694
|
*/
|
|
340
695
|
function parseSecretInput(input, useZodiacPassword, source) {
|
|
341
696
|
if (useZodiacPassword) {
|
|
342
|
-
|
|
343
|
-
try {
|
|
344
|
-
const secretCodes = (0, core_1.decodePassword)(input);
|
|
345
|
-
console.log(`š Zodiac password decoded to: ${secretCodes.join(' ')}`);
|
|
346
|
-
return secretCodes;
|
|
347
|
-
}
|
|
348
|
-
catch (error) {
|
|
349
|
-
throw new Error(`Failed to decode zodiac password${source ? ` from ${source}` : ''}: ${error instanceof Error ? error.message : String(error)}`);
|
|
350
|
-
}
|
|
697
|
+
return decodeZodiacPassword(input, source);
|
|
351
698
|
}
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
const hasOnlyBase58Chars = /^[123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz]+$/.test(input);
|
|
356
|
-
if (!hasSpaces && hasOnlyBase58Chars && input.length < 30) {
|
|
357
|
-
// Looks like a zodiac password, try to decode it
|
|
358
|
-
console.log(`š Auto-detecting zodiac password format${source ? ` in ${source}` : ''}...`);
|
|
359
|
-
try {
|
|
360
|
-
const secretCodes = (0, core_1.decodePassword)(input);
|
|
361
|
-
console.log(`š Zodiac password decoded to: ${secretCodes.join(' ')}`);
|
|
362
|
-
return secretCodes;
|
|
363
|
-
}
|
|
364
|
-
catch (error) {
|
|
365
|
-
throw new Error(`${source ? 'File content' : 'Input'} appears to be zodiac password but failed to decode: ${error instanceof Error ? error.message : String(error)}. Use --zodiac-password flag or ${source ? 'ensure file contains numbers' : 'provide numbers instead'}.`);
|
|
366
|
-
}
|
|
367
|
-
}
|
|
368
|
-
else {
|
|
369
|
-
// Parse as numbers
|
|
370
|
-
const codes = input
|
|
371
|
-
.split(/\s+/)
|
|
372
|
-
.filter((item) => item.length > 0)
|
|
373
|
-
.map((code) => parseInt(code.trim()))
|
|
374
|
-
.filter((code) => !isNaN(code));
|
|
375
|
-
return codes;
|
|
376
|
-
}
|
|
699
|
+
const format = detectInputFormat(input);
|
|
700
|
+
if (format === 'zodiac-password') {
|
|
701
|
+
return decodeZodiacPasswordWithAutoDetect(input, source);
|
|
377
702
|
}
|
|
703
|
+
return parseNumbers(input);
|
|
378
704
|
}
|
|
705
|
+
/**
|
|
706
|
+
* Displays a formatted table showing the complete transformation process
|
|
707
|
+
* Shows original words through each transformation step to final shuffled codes
|
|
708
|
+
*/
|
|
379
709
|
function displayTable(originalWords, mappedNumbers, offsetNumbers, shuffledNumbers, base58Values, zodiacSign) {
|
|
380
710
|
console.log('\nš SEED PHRASE TRANSFORMATION TABLE');
|
|
381
|
-
console.log('
|
|
382
|
-
console.log(
|
|
383
|
-
console.log('
|
|
711
|
+
console.log('================================================================');
|
|
712
|
+
console.log(`# | Original Words | Mapped | Offset | ${zodiacSign} Shuffled | Base58`);
|
|
713
|
+
console.log('-----|------------------|--------|--------|----------------|--------');
|
|
384
714
|
for (let i = 0; i < originalWords.length; i++) {
|
|
385
|
-
const
|
|
386
|
-
const
|
|
387
|
-
const
|
|
388
|
-
const
|
|
389
|
-
const
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
console.log(
|
|
394
|
-
console.log(`Original seed phrase: ${originalWords.join(' ')}`);
|
|
715
|
+
const num = String(i + 1).padStart(4);
|
|
716
|
+
const word = originalWords[i].padEnd(16);
|
|
717
|
+
const mapped = String(mappedNumbers[i]).padStart(6);
|
|
718
|
+
const offset = String(offsetNumbers[i]).padStart(6);
|
|
719
|
+
const shuffled = String(shuffledNumbers[i]).padStart(14);
|
|
720
|
+
const base58 = base58Values[i].padStart(6);
|
|
721
|
+
console.log(`${num} | ${word} ${mapped} ${offset} ${shuffled} ${base58}`);
|
|
722
|
+
}
|
|
723
|
+
console.log('-----|------------------|--------|--------|----------------|--------');
|
|
395
724
|
}
|
|
725
|
+
/**
|
|
726
|
+
* Main entry point for the BIP39 Zodiac CLI
|
|
727
|
+
* Handles argument parsing, validation, and orchestrates transformation or restoration
|
|
728
|
+
*/
|
|
396
729
|
async function main() {
|
|
397
730
|
try {
|
|
398
731
|
const parser = createParser();
|
|
@@ -425,33 +758,149 @@ async function main() {
|
|
|
425
758
|
if (args.restore) {
|
|
426
759
|
console.log('š RESTORING ORIGINAL SEED PHRASE FROM SECRET CODES');
|
|
427
760
|
console.log('==================================================');
|
|
761
|
+
const colorValue = COLOR_VALUES[args.color];
|
|
762
|
+
const isAdd = args.operation === 'add';
|
|
428
763
|
let secretCodes;
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
764
|
+
let inputString = '';
|
|
765
|
+
// Handle old import methods
|
|
766
|
+
if (args.codes) {
|
|
767
|
+
// --codes: Secret codes (numbers from string or file)
|
|
768
|
+
console.log('š„ Processing secret codes...');
|
|
769
|
+
if (args.load) {
|
|
770
|
+
// When --load is present, args.codes contains the file path
|
|
771
|
+
console.log(`š Loading from file: ${args.codes}`);
|
|
772
|
+
const fileContent = (0, core_1.loadArrayFromFile)(args.codes);
|
|
773
|
+
inputString = fileContent.join(' ').trim();
|
|
774
|
+
secretCodes = parseNumbers(inputString);
|
|
775
|
+
}
|
|
776
|
+
else {
|
|
777
|
+
// Direct input as string
|
|
778
|
+
secretCodes = parseNumbers(args.codes);
|
|
779
|
+
}
|
|
780
|
+
(0, core_1.validateBip39Indices)(secretCodes);
|
|
432
781
|
}
|
|
433
|
-
else if (args.
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
782
|
+
else if (args.zodiacPwd) {
|
|
783
|
+
// --zodiacPwd: Plain unencrypted zodiac password (from string or file)
|
|
784
|
+
console.log('š„ Processing plain zodiac password...');
|
|
785
|
+
let passwordInput;
|
|
786
|
+
if (args.load) {
|
|
787
|
+
// When --load is present, args.zodiacPwd contains the file path
|
|
788
|
+
console.log(`š Loading from file: ${args.zodiacPwd}`);
|
|
789
|
+
passwordInput = (0, core_1.loadArrayFromFile)(args.zodiacPwd).join('').trim();
|
|
790
|
+
}
|
|
791
|
+
else {
|
|
792
|
+
// Direct input as string
|
|
793
|
+
passwordInput = args.zodiacPwd;
|
|
794
|
+
}
|
|
795
|
+
secretCodes = (0, core_1.decodePassword)(passwordInput);
|
|
796
|
+
}
|
|
797
|
+
else if (args.encZodiacPwd) {
|
|
798
|
+
// --encZodiacPwd: Single encrypted password (from string or file)
|
|
799
|
+
console.log('š„ Processing encrypted zodiac password...');
|
|
800
|
+
let encryptedInput;
|
|
801
|
+
if (args.load) {
|
|
802
|
+
// When --load is present, args.encZodiacPwd contains the file path
|
|
803
|
+
console.log(`š Loading from file: ${args.encZodiacPwd}`);
|
|
804
|
+
encryptedInput = (0, core_1.loadArrayFromFile)(args.encZodiacPwd).join('').trim();
|
|
805
|
+
}
|
|
806
|
+
else {
|
|
807
|
+
// Direct input as string
|
|
808
|
+
encryptedInput = args.encZodiacPwd;
|
|
809
|
+
}
|
|
810
|
+
// Use custom passphrase if provided, otherwise derive from memory keys
|
|
811
|
+
const memoryKeys = args.passphrase
|
|
812
|
+
? args.passphrase
|
|
813
|
+
: deriveMemoryKeys(args.color, birthday, zodiacSign, args.operation);
|
|
814
|
+
if (args.passphrase) {
|
|
815
|
+
console.log(` Using custom passphrase`);
|
|
816
|
+
}
|
|
817
|
+
else {
|
|
818
|
+
console.log(` Memory keys: ${memoryKeys} (${args.operation} operation)`);
|
|
819
|
+
}
|
|
820
|
+
const decryptionResult = (0, core_1.decryptString)(encryptedInput, memoryKeys);
|
|
821
|
+
const decryptedPassword = decryptionResult.decrypted;
|
|
822
|
+
console.log(`ā
Decryption successful!`);
|
|
823
|
+
console.log(`š Decrypted zodiac password: ${decryptedPassword}`);
|
|
824
|
+
secretCodes = (0, core_1.decodePassword)(decryptedPassword);
|
|
825
|
+
}
|
|
826
|
+
else if (args.encZodiacPwdSplit) {
|
|
827
|
+
// --encZodiacPwdSplit: Comma-separated split parts or from file (2 lines)
|
|
828
|
+
console.log('š„ Processing encrypted zodiac password (split parts)...');
|
|
829
|
+
let part1;
|
|
830
|
+
let part2;
|
|
831
|
+
if (args.load) {
|
|
832
|
+
// When --load is present, args.encZodiacPwdSplit contains the file path
|
|
833
|
+
console.log(`š Loading from file: ${args.encZodiacPwdSplit}`);
|
|
834
|
+
const fileContent = (0, core_1.loadArrayFromFile)(args.encZodiacPwdSplit);
|
|
835
|
+
if (fileContent.length === 2) {
|
|
836
|
+
// Two lines: each line is a part
|
|
837
|
+
part1 = fileContent[0].trim();
|
|
838
|
+
part2 = fileContent[1].trim();
|
|
839
|
+
}
|
|
840
|
+
else if (fileContent.length === 1 && fileContent[0].includes(',')) {
|
|
841
|
+
// One line with comma: split by comma
|
|
842
|
+
const parts = fileContent[0].split(',').map((p) => p.trim());
|
|
843
|
+
if (parts.length !== 2) {
|
|
844
|
+
throw new Error(`Expected 2 comma-separated parts, got ${parts.length}`);
|
|
845
|
+
}
|
|
846
|
+
;
|
|
847
|
+
[part1, part2] = parts;
|
|
848
|
+
}
|
|
849
|
+
else {
|
|
850
|
+
throw new Error(`Expected file to contain either 2 lines or 1 line with comma-separated parts, got ${fileContent.length} line(s)`);
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
else {
|
|
854
|
+
// Direct input as comma-separated string
|
|
855
|
+
const parts = args.encZodiacPwdSplit.split(',').map((p) => p.trim());
|
|
856
|
+
if (parts.length !== 2) {
|
|
857
|
+
throw new Error(`Expected 2 comma-separated parts, got ${parts.length}`);
|
|
858
|
+
}
|
|
859
|
+
;
|
|
860
|
+
[part1, part2] = parts;
|
|
861
|
+
}
|
|
862
|
+
console.log(`š Combining split parts...`);
|
|
863
|
+
console.log(` Split point: ${birthday} + ${colorValue} = ${birthday + colorValue}`);
|
|
864
|
+
const combinedPassword = combineZodiacPassword(part1, part2, birthday, colorValue);
|
|
865
|
+
console.log(`ā
Parts combined successfully!`);
|
|
866
|
+
// Use custom passphrase if provided, otherwise derive from memory keys
|
|
867
|
+
const memoryKeys = args.passphrase
|
|
868
|
+
? args.passphrase
|
|
869
|
+
: deriveMemoryKeys(args.color, birthday, zodiacSign, args.operation);
|
|
870
|
+
console.log(`\nš Decrypting zodiac password...`);
|
|
871
|
+
if (args.passphrase) {
|
|
872
|
+
console.log(` Using custom passphrase`);
|
|
873
|
+
}
|
|
874
|
+
else {
|
|
875
|
+
console.log(` Memory keys: ${memoryKeys} (${args.operation} operation)`);
|
|
876
|
+
}
|
|
877
|
+
const decryptionResult = (0, core_1.decryptString)(combinedPassword, memoryKeys);
|
|
878
|
+
const decryptedPassword = decryptionResult.decrypted;
|
|
879
|
+
console.log(`ā
Decryption successful!`);
|
|
880
|
+
console.log(`š Decrypted zodiac password: ${decryptedPassword}`);
|
|
881
|
+
secretCodes = (0, core_1.decodePassword)(decryptedPassword);
|
|
437
882
|
}
|
|
438
883
|
else {
|
|
439
|
-
throw new Error('No
|
|
884
|
+
throw new Error('No restore input method specified. Use --codes, --zodiacPwd, --encZodiacPwd, or --encZodiacPwdSplit');
|
|
440
885
|
}
|
|
441
|
-
(0, core_1.validateBip39Indices)(secretCodes);
|
|
442
886
|
console.log(`š„ Input secret codes: ${secretCodes.join(' ')}`);
|
|
443
|
-
// Reverse the process: unshuffle ā remove offset ā get original words
|
|
887
|
+
// Reverse the process: unshuffle ā remove color offset ā remove birthday offset ā get original words
|
|
444
888
|
console.log(`š Unshuffling with ${zodiacSign}...`);
|
|
445
889
|
const unshuffleResult = (0, core_1.unshuffleByZodiac)(secretCodes, zodiacSign);
|
|
446
890
|
if (!unshuffleResult.isValid) {
|
|
447
891
|
throw new Error('Failed to unshuffle - invalid codes or wrong zodiac sign');
|
|
448
892
|
}
|
|
449
|
-
const
|
|
450
|
-
console.log(`š Removing
|
|
451
|
-
// Apply the REVERSE offset operation for restoration
|
|
452
|
-
const
|
|
453
|
-
? (0, core_1.subtractOffset)(
|
|
454
|
-
: (0, core_1.addOffset)(
|
|
893
|
+
const afterUnshuffleNumbers = unshuffleResult.restored;
|
|
894
|
+
console.log(`š Removing color offset ${args.color} (${isAdd ? '+' : '-'}${colorValue})...`);
|
|
895
|
+
// Apply the REVERSE color offset operation for restoration
|
|
896
|
+
const afterColorRemovalNumbers = isAdd
|
|
897
|
+
? (0, core_1.subtractOffset)(afterUnshuffleNumbers, colorValue)
|
|
898
|
+
: (0, core_1.addOffset)(afterUnshuffleNumbers, colorValue);
|
|
899
|
+
console.log(`š Removing birthday offset (${isAdd ? '+' : '-'}${birthday})...`);
|
|
900
|
+
// Apply the REVERSE birthday offset operation for restoration
|
|
901
|
+
const originalNumbers = isAdd
|
|
902
|
+
? (0, core_1.subtractOffset)(afterColorRemovalNumbers, birthday)
|
|
903
|
+
: (0, core_1.addOffset)(afterColorRemovalNumbers, birthday);
|
|
455
904
|
const originalWords = (0, core_1.indicesToWords)(originalNumbers);
|
|
456
905
|
(0, core_1.validateBip39Words)(originalWords);
|
|
457
906
|
console.log('\nā
RESTORATION COMPLETE!');
|
|
@@ -481,58 +930,138 @@ async function main() {
|
|
|
481
930
|
console.log('===================================');
|
|
482
931
|
console.log(`š„ Input seed phrase: ${originalWords.join(' ')}`);
|
|
483
932
|
}
|
|
484
|
-
|
|
485
|
-
|
|
933
|
+
const colorValue = COLOR_VALUES[args.color];
|
|
934
|
+
const isAdd = args.operation === 'add';
|
|
935
|
+
// Process: words ā numbers ā birthday offset ā color offset ā shuffle
|
|
936
|
+
console.log(`\nš TRANSFORMATION PROCESS (${isAdd ? 'ADD (+)' : 'SUBTRACT (-)'} ${birthday}, ${args.color.toUpperCase()}(${isAdd ? '+' : '-'}${colorValue}), ${zodiacSign.toUpperCase()})`);
|
|
486
937
|
console.log('='.repeat(70));
|
|
487
938
|
// Step 1: Convert words to numbers
|
|
488
939
|
const mappedNumbers = (0, core_1.wordsToIndices)(originalWords);
|
|
489
940
|
console.log(`š Step 1: Words ā Numbers`);
|
|
490
941
|
// Step 2: Apply birthday offset
|
|
491
|
-
const
|
|
942
|
+
const birthdayOffsetNumbers = isAdd
|
|
492
943
|
? (0, core_1.addOffset)(mappedNumbers, birthday)
|
|
493
944
|
: (0, core_1.subtractOffset)(mappedNumbers, birthday);
|
|
494
|
-
console.log(`š Step 2: ${
|
|
495
|
-
// Step 3: Apply
|
|
496
|
-
const
|
|
945
|
+
console.log(`š Step 2: ${isAdd ? 'Add' : 'Subtract'} birthday offset (${birthday})`);
|
|
946
|
+
// Step 3: Apply color offset
|
|
947
|
+
const colorOffsetNumbers = isAdd
|
|
948
|
+
? (0, core_1.addOffset)(birthdayOffsetNumbers, colorValue)
|
|
949
|
+
: (0, core_1.subtractOffset)(birthdayOffsetNumbers, colorValue);
|
|
950
|
+
console.log(`š Step 3: ${isAdd ? 'Add' : 'Subtract'} color offset ${args.color} (${colorValue})`);
|
|
951
|
+
// Step 4: Apply zodiac shuffle
|
|
952
|
+
const shuffleResult = (0, core_1.shuffleByZodiac)(colorOffsetNumbers, zodiacSign);
|
|
497
953
|
const shuffledNumbers = shuffleResult.shuffled;
|
|
498
|
-
console.log(`š Step
|
|
954
|
+
console.log(`š Step 4: Shuffle with ${zodiacSign}`);
|
|
499
955
|
// Generate zodiac password and get individual Base58 values
|
|
500
|
-
let
|
|
956
|
+
let unencryptedZodiacPassword;
|
|
501
957
|
let base58Values;
|
|
502
958
|
try {
|
|
503
959
|
const passwordResult = (0, core_1.encodePassword)(shuffledNumbers);
|
|
504
|
-
|
|
960
|
+
unencryptedZodiacPassword = passwordResult.password;
|
|
505
961
|
base58Values = passwordResult.individual;
|
|
506
962
|
}
|
|
507
963
|
catch (error) {
|
|
508
964
|
throw new Error(`Failed to generate zodiac password: ${error instanceof Error ? error.message : String(error)}`);
|
|
509
965
|
}
|
|
510
|
-
//
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
966
|
+
// Encrypt zodiac password (ALWAYS)
|
|
967
|
+
let zodiacPassword;
|
|
968
|
+
let splitResult;
|
|
969
|
+
let memoryKeys;
|
|
970
|
+
try {
|
|
971
|
+
// Use custom passphrase if provided, otherwise derive from memory keys
|
|
972
|
+
if (args.passphrase) {
|
|
973
|
+
memoryKeys = args.passphrase;
|
|
974
|
+
console.log(`\nš Encrypting zodiac password...`);
|
|
975
|
+
console.log(` Using custom passphrase`);
|
|
976
|
+
}
|
|
977
|
+
else {
|
|
978
|
+
memoryKeys = deriveMemoryKeys(args.color, birthday, zodiacSign, args.operation);
|
|
979
|
+
console.log(`\nš Encrypting zodiac password...`);
|
|
980
|
+
console.log(` Memory keys: ${memoryKeys} (${args.operation} operation)`);
|
|
981
|
+
}
|
|
982
|
+
const encryptionResult = (0, core_1.encryptString)(unencryptedZodiacPassword, memoryKeys);
|
|
983
|
+
zodiacPassword = encryptionResult.encrypted;
|
|
984
|
+
console.log(`ā
Encryption complete!`);
|
|
985
|
+
// Split the encrypted password using birthday + color
|
|
986
|
+
console.log(`\nš Splitting encrypted password at position ${birthday} + ${colorValue} = ${birthday + colorValue}...`);
|
|
987
|
+
splitResult = splitZodiacPassword(zodiacPassword, birthday, colorValue);
|
|
988
|
+
console.log(`ā
Split complete!`);
|
|
989
|
+
}
|
|
990
|
+
catch (error) {
|
|
991
|
+
throw new Error(`Failed to encrypt zodiac password: ${error instanceof Error ? error.message : String(error)}`);
|
|
992
|
+
}
|
|
993
|
+
// Handle export mode - output JSON and exit
|
|
994
|
+
if (args.export) {
|
|
995
|
+
console.log('\nš¦ Generating export data...');
|
|
996
|
+
const bip39Passphrase = args.passphrase || '';
|
|
997
|
+
const exportData = generateExportData(originalWords, splitResult.part1, splitResult.part2, bip39Passphrase);
|
|
998
|
+
const jsonOutput = JSON.stringify(exportData, null, 2);
|
|
999
|
+
try {
|
|
1000
|
+
(0, core_1.saveArrayToFile)([jsonOutput], args.export);
|
|
1001
|
+
console.log(`ā
Export data saved to: ${args.export}`);
|
|
1002
|
+
}
|
|
1003
|
+
catch (error) {
|
|
1004
|
+
console.error(`ā ļø Failed to save to file '${args.export}': ${error instanceof Error ? error.message : String(error)}`);
|
|
1005
|
+
}
|
|
1006
|
+
return;
|
|
1007
|
+
}
|
|
1008
|
+
// Display the transformation table
|
|
1009
|
+
displayTable(originalWords, mappedNumbers, colorOffsetNumbers, // Combined offset (birthday + color applied)
|
|
1010
|
+
shuffledNumbers, base58Values, zodiacSign);
|
|
1011
|
+
// Display inline format below table
|
|
1012
|
+
console.log('\nOriginal Words:');
|
|
1013
|
+
console.log(originalWords.join(' '));
|
|
1014
|
+
console.log('\nMapped Numbers:');
|
|
1015
|
+
console.log(mappedNumbers.join(' '));
|
|
1016
|
+
console.log('\nOffset Applied:');
|
|
1017
|
+
console.log(colorOffsetNumbers.join(' '));
|
|
1018
|
+
console.log(`\n${zodiacSign} Shuffled:`);
|
|
1019
|
+
console.log(shuffledNumbers.join(' '));
|
|
1020
|
+
console.log('\nBase58:');
|
|
1021
|
+
console.log(base58Values.join(' '));
|
|
1022
|
+
// Display password information
|
|
1023
|
+
console.log('\nZodiac Password:');
|
|
1024
|
+
console.log(unencryptedZodiacPassword);
|
|
1025
|
+
console.log('\nEncrypted Zodiac Password:');
|
|
515
1026
|
console.log(zodiacPassword);
|
|
516
|
-
console.log('\
|
|
1027
|
+
console.log('\nEncrypted Zodiac Password (Split Parts):');
|
|
1028
|
+
console.log(`š” Each part contains a portion of the encrypted password padded with random data`);
|
|
1029
|
+
console.log(` Both parts are required to reconstruct the original encrypted password`);
|
|
1030
|
+
console.log(`Part 1: ${splitResult.part1}`);
|
|
1031
|
+
console.log(`Part 2: ${splitResult.part2}`);
|
|
1032
|
+
console.log(`Split point: ${birthday} + ${colorValue} = ${birthday + colorValue}`);
|
|
1033
|
+
const passphraseFlag = args.passphrase
|
|
1034
|
+
? ` --passphrase "${args.passphrase}"`
|
|
1035
|
+
: '';
|
|
517
1036
|
if (args['zodiac-password']) {
|
|
518
|
-
console.log(
|
|
519
|
-
|
|
520
|
-
|
|
1037
|
+
console.log(`\nš To restore, use: bip39zodiac --encZodiacPwdSplit "${splitResult.part1},${splitResult.part2}" --restore --operation=${args.operation} --birthday=${birthday} --zodiac=${args.zodiac} --color=${args.color}${passphraseFlag}`);
|
|
1038
|
+
if (args.passphrase) {
|
|
1039
|
+
console.log(`\nš” Using custom passphrase for encryption`);
|
|
1040
|
+
}
|
|
1041
|
+
else {
|
|
1042
|
+
console.log(`\nš” Memory keys: ${memoryKeys} (${args.operation} operation - auto-derived from your parameters)`);
|
|
1043
|
+
}
|
|
521
1044
|
}
|
|
522
1045
|
else {
|
|
523
|
-
console.log(
|
|
524
|
-
console.log(
|
|
525
|
-
console.log(
|
|
526
|
-
console.log(`š Or with zodiac password: bip39zodiac --seed "${zodiacPassword}" --restore --zodiac-password --${args.red ? 'red' : 'black'} --birthday=${birthday} --zodiac=${args.zodiac}`);
|
|
1046
|
+
console.log(`\nš To restore with codes: bip39zodiac --codes "${shuffledNumbers.join(' ')}" --restore --operation=${args.operation} --birthday=${birthday} --zodiac=${args.zodiac} --color=${args.color}`);
|
|
1047
|
+
console.log(`\nš To restore with zodiac password: bip39zodiac --zodiacPwd "${unencryptedZodiacPassword}" --restore --operation=${args.operation} --birthday=${birthday} --zodiac=${args.zodiac} --color=${args.color}`);
|
|
1048
|
+
console.log(`\nš To restore with encrypted split: bip39zodiac --encZodiacPwdSplit "${splitResult.part1},${splitResult.part2}" --restore --operation=${args.operation} --birthday=${birthday} --zodiac=${args.zodiac} --color=${args.color}${passphraseFlag}`);
|
|
527
1049
|
}
|
|
528
1050
|
// Save to file if --save option provided
|
|
529
1051
|
if (args.save) {
|
|
530
1052
|
try {
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
1053
|
+
if (args['zodiac-password']) {
|
|
1054
|
+
// Save both split parts (part1 on first line, part2 on second line)
|
|
1055
|
+
(0, core_1.saveArrayToFile)([splitResult.part1, splitResult.part2], args.save);
|
|
1056
|
+
console.log(`š¾ Split parts saved to: ${args.save}`);
|
|
1057
|
+
console.log(` Part 1: Line 1`);
|
|
1058
|
+
console.log(` Part 2: Line 2`);
|
|
1059
|
+
}
|
|
1060
|
+
else {
|
|
1061
|
+
// Save numeric codes
|
|
1062
|
+
(0, core_1.saveArrayToFile)([shuffledNumbers.join(' ')], args.save);
|
|
1063
|
+
console.log(`š¾ Results saved to: ${args.save}`);
|
|
1064
|
+
}
|
|
536
1065
|
}
|
|
537
1066
|
catch (error) {
|
|
538
1067
|
console.error(`ā ļø Failed to save to file '${args.save}': ${error instanceof Error ? error.message : String(error)}`);
|