@btc-vision/cli 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.gitattributes +2 -0
- package/.github/dependabot.yml +9 -0
- package/.github/workflows/ci.yml +48 -0
- package/.prettierrc.json +12 -0
- package/CONTRIBUTING.md +56 -0
- package/LICENSE +190 -0
- package/NOTICE +17 -0
- package/README.md +509 -0
- package/SECURITY.md +35 -0
- package/build/commands/AcceptCommand.d.ts +7 -0
- package/build/commands/AcceptCommand.js +110 -0
- package/build/commands/BaseCommand.d.ts +12 -0
- package/build/commands/BaseCommand.js +27 -0
- package/build/commands/CompileCommand.d.ts +7 -0
- package/build/commands/CompileCommand.js +138 -0
- package/build/commands/ConfigCommand.d.ts +17 -0
- package/build/commands/ConfigCommand.js +124 -0
- package/build/commands/DeprecateCommand.d.ts +7 -0
- package/build/commands/DeprecateCommand.js +112 -0
- package/build/commands/InfoCommand.d.ts +10 -0
- package/build/commands/InfoCommand.js +223 -0
- package/build/commands/InitCommand.d.ts +16 -0
- package/build/commands/InitCommand.js +336 -0
- package/build/commands/InstallCommand.d.ts +7 -0
- package/build/commands/InstallCommand.js +130 -0
- package/build/commands/KeygenCommand.d.ts +13 -0
- package/build/commands/KeygenCommand.js +133 -0
- package/build/commands/ListCommand.d.ts +7 -0
- package/build/commands/ListCommand.js +117 -0
- package/build/commands/LoginCommand.d.ts +9 -0
- package/build/commands/LoginCommand.js +139 -0
- package/build/commands/LogoutCommand.d.ts +7 -0
- package/build/commands/LogoutCommand.js +57 -0
- package/build/commands/PublishCommand.d.ts +7 -0
- package/build/commands/PublishCommand.js +163 -0
- package/build/commands/SearchCommand.d.ts +7 -0
- package/build/commands/SearchCommand.js +97 -0
- package/build/commands/SignCommand.d.ts +7 -0
- package/build/commands/SignCommand.js +80 -0
- package/build/commands/TransferCommand.d.ts +8 -0
- package/build/commands/TransferCommand.js +179 -0
- package/build/commands/UndeprecateCommand.d.ts +7 -0
- package/build/commands/UndeprecateCommand.js +95 -0
- package/build/commands/UpdateCommand.d.ts +7 -0
- package/build/commands/UpdateCommand.js +130 -0
- package/build/commands/VerifyCommand.d.ts +7 -0
- package/build/commands/VerifyCommand.js +167 -0
- package/build/commands/WhoamiCommand.d.ts +7 -0
- package/build/commands/WhoamiCommand.js +84 -0
- package/build/index.d.ts +2 -0
- package/build/index.js +64 -0
- package/build/lib/PackageRegistry.abi.d.ts +2 -0
- package/build/lib/PackageRegistry.abi.js +356 -0
- package/build/lib/binary.d.ts +16 -0
- package/build/lib/binary.js +165 -0
- package/build/lib/config.d.ts +11 -0
- package/build/lib/config.js +160 -0
- package/build/lib/credentials.d.ts +10 -0
- package/build/lib/credentials.js +89 -0
- package/build/lib/ipfs.d.ts +16 -0
- package/build/lib/ipfs.js +209 -0
- package/build/lib/manifest.d.ts +14 -0
- package/build/lib/manifest.js +88 -0
- package/build/lib/provider.d.ts +9 -0
- package/build/lib/provider.js +48 -0
- package/build/lib/registry.d.ts +58 -0
- package/build/lib/registry.js +197 -0
- package/build/lib/wallet.d.ts +32 -0
- package/build/lib/wallet.js +114 -0
- package/build/types/PackageRegistry.d.ts +177 -0
- package/build/types/PackageRegistry.js +1 -0
- package/build/types/index.d.ts +30 -0
- package/build/types/index.js +52 -0
- package/eslint.config.js +41 -0
- package/gulpfile.js +41 -0
- package/package.json +83 -0
- package/src/commands/AcceptCommand.ts +151 -0
- package/src/commands/BaseCommand.ts +59 -0
- package/src/commands/CompileCommand.ts +196 -0
- package/src/commands/ConfigCommand.ts +144 -0
- package/src/commands/DeprecateCommand.ts +156 -0
- package/src/commands/InfoCommand.ts +293 -0
- package/src/commands/InitCommand.ts +465 -0
- package/src/commands/InstallCommand.ts +179 -0
- package/src/commands/KeygenCommand.ts +157 -0
- package/src/commands/ListCommand.ts +169 -0
- package/src/commands/LoginCommand.ts +197 -0
- package/src/commands/LogoutCommand.ts +76 -0
- package/src/commands/PublishCommand.ts +230 -0
- package/src/commands/SearchCommand.ts +141 -0
- package/src/commands/SignCommand.ts +122 -0
- package/src/commands/TransferCommand.ts +235 -0
- package/src/commands/UndeprecateCommand.ts +134 -0
- package/src/commands/UpdateCommand.ts +200 -0
- package/src/commands/VerifyCommand.ts +228 -0
- package/src/commands/WhoamiCommand.ts +113 -0
- package/src/index.ts +86 -0
- package/src/lib/PackageRegistry.abi.json +765 -0
- package/src/lib/PackageRegistry.abi.ts +365 -0
- package/src/lib/binary.ts +336 -0
- package/src/lib/config.ts +265 -0
- package/src/lib/credentials.ts +176 -0
- package/src/lib/ipfs.ts +369 -0
- package/src/lib/manifest.ts +172 -0
- package/src/lib/provider.ts +121 -0
- package/src/lib/registry.ts +464 -0
- package/src/lib/wallet.ts +271 -0
- package/src/types/PackageRegistry.ts +344 -0
- package/src/types/index.ts +145 -0
- package/tsconfig.json +25 -0
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Keygen command - Generate MLDSA keypairs and mnemonics
|
|
3
|
+
*
|
|
4
|
+
* @module commands/KeygenCommand
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { Command } from 'commander';
|
|
8
|
+
import * as fs from 'fs';
|
|
9
|
+
import { BaseCommand } from './BaseCommand.js';
|
|
10
|
+
import { computePublicKeyHash, generateMLDSAKeypair, generateMnemonic } from '../lib/wallet.js';
|
|
11
|
+
import { isValidMldsaLevel } from '../lib/credentials.js';
|
|
12
|
+
|
|
13
|
+
export class KeygenCommand extends BaseCommand {
|
|
14
|
+
constructor() {
|
|
15
|
+
super('keygen', 'Generate cryptographic keys');
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
protected configure(): void {
|
|
19
|
+
this.command
|
|
20
|
+
.addCommand(this.createMnemonicCommand())
|
|
21
|
+
.addCommand(this.createMldsaCommand())
|
|
22
|
+
.addCommand(this.createInfoCommand());
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
private createMnemonicCommand(): Command {
|
|
26
|
+
return new Command('mnemonic')
|
|
27
|
+
.description('Generate a new BIP-39 mnemonic phrase')
|
|
28
|
+
.option('-o, --output <file>', 'Write mnemonic to file (secure permissions)')
|
|
29
|
+
.action((options: { output?: string }) => this.handleMnemonic(options));
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
private createMldsaCommand(): Command {
|
|
33
|
+
return new Command('mldsa')
|
|
34
|
+
.description('Generate a standalone MLDSA keypair')
|
|
35
|
+
.option('-l, --level <level>', 'MLDSA security level (44, 65, 87)', '44')
|
|
36
|
+
.option('-o, --output <prefix>', 'Write keys to files with prefix')
|
|
37
|
+
.option('--json', 'Output as JSON')
|
|
38
|
+
.action((options: { level: string; output?: string; json?: boolean }) => {
|
|
39
|
+
this.handleMldsa(options);
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
private createInfoCommand(): Command {
|
|
44
|
+
return new Command('info')
|
|
45
|
+
.description('Show information about MLDSA key sizes')
|
|
46
|
+
.action(() => this.handleInfo());
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
private handleMnemonic(options: { output?: string }): void {
|
|
50
|
+
try {
|
|
51
|
+
const mnemonic = generateMnemonic();
|
|
52
|
+
|
|
53
|
+
if (options.output) {
|
|
54
|
+
fs.writeFileSync(options.output, mnemonic + '\n', { mode: 0o600 });
|
|
55
|
+
this.logger.success(`Mnemonic saved to: ${options.output}`);
|
|
56
|
+
this.logger.warn('Keep this file secure and backed up!');
|
|
57
|
+
} else {
|
|
58
|
+
this.logger.info('\nNew BIP-39 Mnemonic Phrase:\n');
|
|
59
|
+
this.logger.log(mnemonic);
|
|
60
|
+
this.logger.log('');
|
|
61
|
+
this.logger.warn('IMPORTANT: Write down these words and store them securely.');
|
|
62
|
+
this.logger.warn('Anyone with this phrase can access your wallet.');
|
|
63
|
+
this.logger.warn('Never share this phrase with anyone.');
|
|
64
|
+
}
|
|
65
|
+
} catch (error) {
|
|
66
|
+
this.exitWithError(this.formatError(error));
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
private handleMldsa(options: { level: string; output?: string; json?: boolean }): void {
|
|
71
|
+
try {
|
|
72
|
+
const levelNum = parseInt(options.level, 10);
|
|
73
|
+
if (!isValidMldsaLevel(levelNum)) {
|
|
74
|
+
this.exitWithError(`Invalid MLDSA level: ${options.level}. Valid: 44, 65, 87`);
|
|
75
|
+
return; // Unreachable, but helps TypeScript
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
this.logger.info(`Generating MLDSA-${levelNum} keypair...`);
|
|
79
|
+
|
|
80
|
+
const keypair = generateMLDSAKeypair(levelNum);
|
|
81
|
+
const publicKeyHash = computePublicKeyHash(keypair.publicKey);
|
|
82
|
+
|
|
83
|
+
if (options.output) {
|
|
84
|
+
const privateKeyPath = `${options.output}.private.key`;
|
|
85
|
+
const publicKeyPath = `${options.output}.public.key`;
|
|
86
|
+
|
|
87
|
+
fs.writeFileSync(privateKeyPath, keypair.privateKey.toString('hex') + '\n', {
|
|
88
|
+
mode: 0o600,
|
|
89
|
+
});
|
|
90
|
+
fs.writeFileSync(publicKeyPath, keypair.publicKey.toString('hex') + '\n', {
|
|
91
|
+
mode: 0o644,
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
this.logger.success('Keys generated successfully!');
|
|
95
|
+
this.logger.info(` Private key: ${privateKeyPath}`);
|
|
96
|
+
this.logger.info(` Public key: ${publicKeyPath}`);
|
|
97
|
+
this.logger.log('');
|
|
98
|
+
this.logger.log(`Public Key Hash: ${publicKeyHash}`);
|
|
99
|
+
this.logger.log('');
|
|
100
|
+
this.logger.warn('IMPORTANT: Keep the private key secure!');
|
|
101
|
+
} else if (options.json) {
|
|
102
|
+
const output = {
|
|
103
|
+
level: levelNum,
|
|
104
|
+
privateKey: keypair.privateKey.toString('hex'),
|
|
105
|
+
publicKey: keypair.publicKey.toString('hex'),
|
|
106
|
+
publicKeyHash,
|
|
107
|
+
privateKeySize: keypair.privateKey.length,
|
|
108
|
+
publicKeySize: keypair.publicKey.length,
|
|
109
|
+
};
|
|
110
|
+
this.logger.log(JSON.stringify(output, null, 2));
|
|
111
|
+
} else {
|
|
112
|
+
this.logger.info(`\nMLDSA-${levelNum} Keypair:\n`);
|
|
113
|
+
this.logger.log(`Public Key Hash: ${publicKeyHash}`);
|
|
114
|
+
this.logger.log(`Public Key Size: ${keypair.publicKey.length} bytes`);
|
|
115
|
+
this.logger.log(`Private Key Size: ${keypair.privateKey.length} bytes`);
|
|
116
|
+
this.logger.log('');
|
|
117
|
+
this.logger.log('Public Key (hex):');
|
|
118
|
+
this.logger.log(keypair.publicKey.toString('hex'));
|
|
119
|
+
this.logger.log('');
|
|
120
|
+
this.logger.log('Private Key (hex):');
|
|
121
|
+
this.logger.log(keypair.privateKey.toString('hex'));
|
|
122
|
+
this.logger.log('');
|
|
123
|
+
this.logger.warn('IMPORTANT: Store the private key securely!');
|
|
124
|
+
this.logger.warn('Use --output <prefix> to save to files.');
|
|
125
|
+
}
|
|
126
|
+
} catch (error) {
|
|
127
|
+
this.exitWithError(this.formatError(error));
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
private handleInfo(): void {
|
|
132
|
+
this.logger.info('\nMLDSA Key Sizes:\n');
|
|
133
|
+
this.logger.log('─'.repeat(60));
|
|
134
|
+
this.logger.log(
|
|
135
|
+
`${'Level'.padEnd(12)}${'Public Key'.padEnd(15)}${'Private Key'.padEnd(15)}${'Signature'.padEnd(15)}`,
|
|
136
|
+
);
|
|
137
|
+
this.logger.log('─'.repeat(60));
|
|
138
|
+
this.logger.log(
|
|
139
|
+
`${'MLDSA-44'.padEnd(12)}${'1,312 bytes'.padEnd(15)}${'2,560 bytes'.padEnd(15)}${'2,420 bytes'.padEnd(15)}`,
|
|
140
|
+
);
|
|
141
|
+
this.logger.log(
|
|
142
|
+
`${'MLDSA-65'.padEnd(12)}${'1,952 bytes'.padEnd(15)}${'4,032 bytes'.padEnd(15)}${'3,309 bytes'.padEnd(15)}`,
|
|
143
|
+
);
|
|
144
|
+
this.logger.log(
|
|
145
|
+
`${'MLDSA-87'.padEnd(12)}${'2,592 bytes'.padEnd(15)}${'4,896 bytes'.padEnd(15)}${'4,627 bytes'.padEnd(15)}`,
|
|
146
|
+
);
|
|
147
|
+
this.logger.log('─'.repeat(60));
|
|
148
|
+
this.logger.log('');
|
|
149
|
+
this.logger.log('Security levels:');
|
|
150
|
+
this.logger.log(' MLDSA-44: ~128-bit security (fastest, smallest)');
|
|
151
|
+
this.logger.log(' MLDSA-65: ~192-bit security (balanced)');
|
|
152
|
+
this.logger.log(' MLDSA-87: ~256-bit security (highest security)');
|
|
153
|
+
this.logger.log('');
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export const keygenCommand = new KeygenCommand().getCommand();
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* List command - List installed plugins
|
|
3
|
+
*
|
|
4
|
+
* @module commands/ListCommand
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import * as fs from 'fs';
|
|
8
|
+
import * as path from 'path';
|
|
9
|
+
import { BaseCommand } from './BaseCommand.js';
|
|
10
|
+
import { formatFileSize, parseOpnetBinary } from '../lib/binary.js';
|
|
11
|
+
import { CLIMldsaLevel } from '../types/index.js';
|
|
12
|
+
|
|
13
|
+
interface ListOptions {
|
|
14
|
+
dir?: string;
|
|
15
|
+
json?: boolean;
|
|
16
|
+
verbose?: boolean;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
interface PluginInfo {
|
|
20
|
+
file: string;
|
|
21
|
+
name: string;
|
|
22
|
+
version: string;
|
|
23
|
+
type: string;
|
|
24
|
+
size: number;
|
|
25
|
+
signed: boolean;
|
|
26
|
+
mldsaLevel: CLIMldsaLevel;
|
|
27
|
+
author: string;
|
|
28
|
+
description?: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export class ListCommand extends BaseCommand {
|
|
32
|
+
constructor() {
|
|
33
|
+
super('list', 'List installed plugins');
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
protected configure(): void {
|
|
37
|
+
this.command
|
|
38
|
+
.alias('ls')
|
|
39
|
+
.option('-d, --dir <path>', 'Plugins directory (default: ./plugins/)')
|
|
40
|
+
.option('--json', 'Output as JSON')
|
|
41
|
+
.option('-v, --verbose', 'Show detailed information')
|
|
42
|
+
.action((options?: ListOptions) => this.execute(options));
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
private execute(options?: ListOptions): void {
|
|
46
|
+
try {
|
|
47
|
+
const pluginsDir = options?.dir || path.join(process.cwd(), 'plugins');
|
|
48
|
+
|
|
49
|
+
if (!fs.existsSync(pluginsDir)) {
|
|
50
|
+
if (options?.json) {
|
|
51
|
+
this.logger.log(JSON.stringify({ plugins: [], directory: pluginsDir }));
|
|
52
|
+
} else {
|
|
53
|
+
this.logger.warn('No plugins directory found.');
|
|
54
|
+
this.logger.info(`Expected: ${pluginsDir}`);
|
|
55
|
+
}
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Find all .opnet files
|
|
60
|
+
const files = fs.readdirSync(pluginsDir).filter((f) => f.endsWith('.opnet'));
|
|
61
|
+
|
|
62
|
+
if (files.length === 0) {
|
|
63
|
+
if (options?.json) {
|
|
64
|
+
this.logger.log(JSON.stringify({ plugins: [], directory: pluginsDir }));
|
|
65
|
+
} else {
|
|
66
|
+
this.logger.warn('No plugins installed.');
|
|
67
|
+
}
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const plugins: PluginInfo[] = [];
|
|
72
|
+
|
|
73
|
+
// Parse each plugin
|
|
74
|
+
for (const file of files) {
|
|
75
|
+
const filePath = path.join(pluginsDir, file);
|
|
76
|
+
|
|
77
|
+
try {
|
|
78
|
+
const data = fs.readFileSync(filePath);
|
|
79
|
+
const parsed = parseOpnetBinary(data);
|
|
80
|
+
const isUnsigned = parsed.publicKey.every((b) => b === 0);
|
|
81
|
+
const mldsaLevel = ([44, 65, 87] as const)[parsed.mldsaLevel];
|
|
82
|
+
|
|
83
|
+
plugins.push({
|
|
84
|
+
file,
|
|
85
|
+
name: parsed.metadata.name,
|
|
86
|
+
version: parsed.metadata.version,
|
|
87
|
+
type: parsed.metadata.pluginType,
|
|
88
|
+
size: data.length,
|
|
89
|
+
signed: !isUnsigned,
|
|
90
|
+
mldsaLevel,
|
|
91
|
+
author: parsed.metadata.author.name,
|
|
92
|
+
description: parsed.metadata.description,
|
|
93
|
+
});
|
|
94
|
+
} catch {
|
|
95
|
+
plugins.push({
|
|
96
|
+
file,
|
|
97
|
+
name: '(invalid)',
|
|
98
|
+
version: '-',
|
|
99
|
+
type: '-',
|
|
100
|
+
size: fs.statSync(filePath).size,
|
|
101
|
+
signed: false,
|
|
102
|
+
mldsaLevel: 44,
|
|
103
|
+
author: '-',
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Sort by name
|
|
109
|
+
plugins.sort((a, b) => a.name.localeCompare(b.name));
|
|
110
|
+
|
|
111
|
+
if (options?.json) {
|
|
112
|
+
this.logger.log(JSON.stringify({ plugins, directory: pluginsDir }, null, 2));
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Display
|
|
117
|
+
this.logger.info('\nInstalled Plugins\n');
|
|
118
|
+
this.logger.info(`Directory: ${pluginsDir}`);
|
|
119
|
+
this.logger.log('');
|
|
120
|
+
|
|
121
|
+
if (options?.verbose) {
|
|
122
|
+
// Detailed list
|
|
123
|
+
for (const plugin of plugins) {
|
|
124
|
+
this.logger.info('─'.repeat(60));
|
|
125
|
+
this.logger.info(`${plugin.name} @ ${plugin.version}`);
|
|
126
|
+
this.logger.info(` Type: ${plugin.type}`);
|
|
127
|
+
this.logger.info(` Size: ${formatFileSize(plugin.size)}`);
|
|
128
|
+
this.logger.info(` Signed: ${plugin.signed ? 'Yes' : 'No'}`);
|
|
129
|
+
this.logger.info(` MLDSA: ${plugin.mldsaLevel}`);
|
|
130
|
+
this.logger.info(` Author: ${plugin.author}`);
|
|
131
|
+
if (plugin.description) {
|
|
132
|
+
this.logger.info(` Desc: ${plugin.description}`);
|
|
133
|
+
}
|
|
134
|
+
this.logger.info(` File: ${plugin.file}`);
|
|
135
|
+
}
|
|
136
|
+
} else {
|
|
137
|
+
// Simple table
|
|
138
|
+
const nameWidth = Math.max(20, ...plugins.map((p) => p.name.length)) + 2;
|
|
139
|
+
const versionWidth = 12;
|
|
140
|
+
const typeWidth = 12;
|
|
141
|
+
const sizeWidth = 10;
|
|
142
|
+
|
|
143
|
+
this.logger.info(
|
|
144
|
+
'Name'.padEnd(nameWidth) +
|
|
145
|
+
'Version'.padEnd(versionWidth) +
|
|
146
|
+
'Type'.padEnd(typeWidth) +
|
|
147
|
+
'Size'.padEnd(sizeWidth) +
|
|
148
|
+
'Signed',
|
|
149
|
+
);
|
|
150
|
+
this.logger.info('─'.repeat(nameWidth + versionWidth + typeWidth + sizeWidth + 8));
|
|
151
|
+
|
|
152
|
+
for (const plugin of plugins) {
|
|
153
|
+
const signedText = plugin.signed ? 'Yes' : 'No';
|
|
154
|
+
this.logger.info(
|
|
155
|
+
`${plugin.name.padEnd(nameWidth)}${plugin.version.padEnd(versionWidth)}${plugin.type.padEnd(typeWidth)}${formatFileSize(plugin.size).padEnd(sizeWidth)}${signedText}`,
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
this.logger.log('');
|
|
161
|
+
this.logger.info(`Total: ${plugins.length} plugin${plugins.length === 1 ? '' : 's'}`);
|
|
162
|
+
this.logger.log('');
|
|
163
|
+
} catch (error) {
|
|
164
|
+
this.exitWithError(this.formatError(error));
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
export const listCommand = new ListCommand().getCommand();
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Login command - Configure wallet credentials
|
|
3
|
+
*
|
|
4
|
+
* @module commands/LoginCommand
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { confirm, password, select } from '@inquirer/prompts';
|
|
8
|
+
import { BaseCommand } from './BaseCommand.js';
|
|
9
|
+
import { isValidMldsaLevel, isValidNetwork, saveCredentials } from '../lib/credentials.js';
|
|
10
|
+
import { CLIWallet, validateMnemonic } from '../lib/wallet.js';
|
|
11
|
+
import { CLICredentials, CLIMldsaLevel, NetworkName } from '../types/index.js';
|
|
12
|
+
|
|
13
|
+
interface LoginOptions {
|
|
14
|
+
mnemonic?: string;
|
|
15
|
+
wif?: string;
|
|
16
|
+
mldsa?: string;
|
|
17
|
+
mldsaLevel: string;
|
|
18
|
+
network: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export class LoginCommand extends BaseCommand {
|
|
22
|
+
constructor() {
|
|
23
|
+
super('login', 'Configure wallet credentials for signing and publishing');
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
protected configure(): void {
|
|
27
|
+
this.command
|
|
28
|
+
.option('-m, --mnemonic <phrase>', 'BIP-39 mnemonic phrase (12 or 24 words)')
|
|
29
|
+
.option('--wif <key>', 'Bitcoin WIF private key (advanced)')
|
|
30
|
+
.option('--mldsa <key>', 'MLDSA private key hex (advanced, requires --wif)')
|
|
31
|
+
.option('-l, --mldsa-level <level>', 'MLDSA security level (44, 65, 87)', '44')
|
|
32
|
+
.option('-n, --network <network>', 'Network (mainnet, testnet, regtest)', 'mainnet')
|
|
33
|
+
.action((options: LoginOptions) => this.execute(options));
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
private async execute(options: LoginOptions): Promise<void> {
|
|
37
|
+
try {
|
|
38
|
+
const credentials = await this.buildCredentials(options);
|
|
39
|
+
|
|
40
|
+
// Load wallet to display identity info
|
|
41
|
+
this.logger.info('Deriving wallet...');
|
|
42
|
+
const wallet = CLIWallet.fromCredentials(credentials);
|
|
43
|
+
|
|
44
|
+
// Display wallet identity for user verification
|
|
45
|
+
this.logger.log('');
|
|
46
|
+
this.logger.info('Wallet Identity');
|
|
47
|
+
this.logger.log('─'.repeat(50));
|
|
48
|
+
this.logger.log(`Network: ${credentials.network}`);
|
|
49
|
+
this.logger.log(`MLDSA Level: MLDSA-${credentials.mldsaLevel}`);
|
|
50
|
+
this.logger.log(`P2TR Address: ${wallet.p2trAddress}`);
|
|
51
|
+
this.logger.log(`MLDSA PubKey Hash: ${wallet.mldsaPublicKeyHash.substring(0, 32)}...`);
|
|
52
|
+
this.logger.log('─'.repeat(50));
|
|
53
|
+
this.logger.log('');
|
|
54
|
+
|
|
55
|
+
// Always require confirmation
|
|
56
|
+
this.logger.warn('Credentials will be stored at ~/.opnet/credentials.json');
|
|
57
|
+
this.logger.warn('with restricted permissions (owner read/write only).');
|
|
58
|
+
this.logger.log('');
|
|
59
|
+
|
|
60
|
+
const confirmed = await confirm({
|
|
61
|
+
message: 'Is this the correct wallet? Save credentials?',
|
|
62
|
+
default: true,
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
if (!confirmed) {
|
|
66
|
+
this.logger.warn('Login cancelled.');
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
saveCredentials(credentials);
|
|
71
|
+
|
|
72
|
+
this.logger.log('');
|
|
73
|
+
this.logger.success('Credentials saved successfully!');
|
|
74
|
+
} catch (error) {
|
|
75
|
+
if (this.isUserCancelled(error)) {
|
|
76
|
+
this.logger.warn('Login cancelled.');
|
|
77
|
+
process.exit(0);
|
|
78
|
+
}
|
|
79
|
+
this.exitWithError(this.formatError(error));
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
private async buildCredentials(options: LoginOptions): Promise<CLICredentials> {
|
|
84
|
+
if (!isValidNetwork(options.network)) {
|
|
85
|
+
this.exitWithError(
|
|
86
|
+
`Invalid network: ${options.network}. Valid: mainnet, testnet, regtest`,
|
|
87
|
+
);
|
|
88
|
+
throw new Error('Unreachable'); // Helps TypeScript
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const mldsaLevelNum = parseInt(options.mldsaLevel, 10);
|
|
92
|
+
if (!isValidMldsaLevel(mldsaLevelNum)) {
|
|
93
|
+
this.exitWithError(`Invalid MLDSA level: ${options.mldsaLevel}. Valid: 44, 65, 87`);
|
|
94
|
+
}
|
|
95
|
+
const mldsaLevel: CLIMldsaLevel = mldsaLevelNum;
|
|
96
|
+
|
|
97
|
+
if (options.mnemonic) {
|
|
98
|
+
if (!validateMnemonic(options.mnemonic)) {
|
|
99
|
+
this.exitWithError('Invalid mnemonic phrase');
|
|
100
|
+
}
|
|
101
|
+
return { mnemonic: options.mnemonic, mldsaLevel, network: options.network };
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (options.wif && options.mldsa) {
|
|
105
|
+
return {
|
|
106
|
+
wif: options.wif,
|
|
107
|
+
mldsaPrivateKey: options.mldsa,
|
|
108
|
+
mldsaLevel,
|
|
109
|
+
network: options.network,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return this.interactiveLogin(options.network, mldsaLevel);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
private async interactiveLogin(
|
|
117
|
+
defaultNetwork: NetworkName,
|
|
118
|
+
defaultLevel: CLIMldsaLevel,
|
|
119
|
+
): Promise<CLICredentials> {
|
|
120
|
+
this.logger.info('OPNet Wallet Configuration\n');
|
|
121
|
+
|
|
122
|
+
const loginMethod = await select({
|
|
123
|
+
message: 'How would you like to authenticate?',
|
|
124
|
+
choices: [
|
|
125
|
+
{
|
|
126
|
+
name: 'Mnemonic phrase (recommended)',
|
|
127
|
+
value: 'mnemonic',
|
|
128
|
+
description: '12 or 24-word BIP-39 phrase for full key derivation',
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
name: 'WIF + MLDSA keys (advanced)',
|
|
132
|
+
value: 'advanced',
|
|
133
|
+
description: 'Separate Bitcoin WIF and MLDSA private keys',
|
|
134
|
+
},
|
|
135
|
+
],
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
const selectedNetwork = (await select({
|
|
139
|
+
message: 'Select network:',
|
|
140
|
+
choices: [
|
|
141
|
+
{ name: 'Mainnet', value: 'mainnet' },
|
|
142
|
+
{ name: 'Testnet', value: 'testnet' },
|
|
143
|
+
{ name: 'Regtest', value: 'regtest' },
|
|
144
|
+
],
|
|
145
|
+
default: defaultNetwork,
|
|
146
|
+
})) as NetworkName;
|
|
147
|
+
|
|
148
|
+
const selectedLevel = (await select({
|
|
149
|
+
message: 'Select MLDSA security level:',
|
|
150
|
+
choices: [
|
|
151
|
+
{
|
|
152
|
+
name: 'MLDSA-44 (Level 2, fastest)',
|
|
153
|
+
value: 44,
|
|
154
|
+
description: '1312 byte public key',
|
|
155
|
+
},
|
|
156
|
+
{
|
|
157
|
+
name: 'MLDSA-65 (Level 3, balanced)',
|
|
158
|
+
value: 65,
|
|
159
|
+
description: '1952 byte public key',
|
|
160
|
+
},
|
|
161
|
+
{
|
|
162
|
+
name: 'MLDSA-87 (Level 5, most secure)',
|
|
163
|
+
value: 87,
|
|
164
|
+
description: '2592 byte public key',
|
|
165
|
+
},
|
|
166
|
+
],
|
|
167
|
+
default: defaultLevel,
|
|
168
|
+
})) as CLIMldsaLevel;
|
|
169
|
+
|
|
170
|
+
if (loginMethod === 'mnemonic') {
|
|
171
|
+
const mnemonic = await password({
|
|
172
|
+
message: 'Enter your mnemonic phrase (12 or 24 words):',
|
|
173
|
+
mask: '*',
|
|
174
|
+
validate: (value) => {
|
|
175
|
+
if (!validateMnemonic(value)) {
|
|
176
|
+
return 'Invalid mnemonic phrase. Please enter a valid 12 or 24-word BIP-39 phrase.';
|
|
177
|
+
}
|
|
178
|
+
return true;
|
|
179
|
+
},
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
return { mnemonic, mldsaLevel: selectedLevel, network: selectedNetwork };
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const wif = await password({ message: 'Enter Bitcoin WIF private key:', mask: '*' });
|
|
186
|
+
const mldsaKey = await password({ message: 'Enter MLDSA private key (hex):', mask: '*' });
|
|
187
|
+
|
|
188
|
+
return {
|
|
189
|
+
wif,
|
|
190
|
+
mldsaPrivateKey: mldsaKey,
|
|
191
|
+
mldsaLevel: selectedLevel,
|
|
192
|
+
network: selectedNetwork,
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
export const loginCommand = new LoginCommand().getCommand();
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Logout command - Remove stored credentials
|
|
3
|
+
*
|
|
4
|
+
* @module commands/LogoutCommand
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { confirm } from '@inquirer/prompts';
|
|
8
|
+
import { BaseCommand } from './BaseCommand.js';
|
|
9
|
+
import { deleteCredentials, getCredentialSource, hasCredentials } from '../lib/credentials.js';
|
|
10
|
+
|
|
11
|
+
interface LogoutOptions {
|
|
12
|
+
yes?: boolean;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export class LogoutCommand extends BaseCommand {
|
|
16
|
+
constructor() {
|
|
17
|
+
super('logout', 'Remove stored wallet credentials');
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
protected configure(): void {
|
|
21
|
+
this.command
|
|
22
|
+
.option('-y, --yes', 'Skip confirmation prompt')
|
|
23
|
+
.action((options: LogoutOptions) => this.execute(options));
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
private async execute(options: LogoutOptions): Promise<void> {
|
|
27
|
+
try {
|
|
28
|
+
if (!hasCredentials()) {
|
|
29
|
+
this.logger.warn('No credentials found.');
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const source = getCredentialSource();
|
|
34
|
+
|
|
35
|
+
if (source.startsWith('environment')) {
|
|
36
|
+
this.logger.warn(`Credentials are set via ${source}.`);
|
|
37
|
+
this.logger.info('To remove them, unset the environment variables:');
|
|
38
|
+
this.logger.info(' unset OPNET_MNEMONIC');
|
|
39
|
+
this.logger.info(' unset OPNET_PRIVATE_KEY');
|
|
40
|
+
this.logger.info(' unset OPNET_MLDSA_KEY');
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (!options.yes) {
|
|
45
|
+
this.logger.warn('This will remove your stored credentials from:');
|
|
46
|
+
this.logger.info(` ${source}`);
|
|
47
|
+
|
|
48
|
+
const confirmed = await confirm({
|
|
49
|
+
message: 'Are you sure you want to logout?',
|
|
50
|
+
default: false,
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
if (!confirmed) {
|
|
54
|
+
this.logger.warn('Logout cancelled.');
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const deleted = deleteCredentials();
|
|
60
|
+
|
|
61
|
+
if (deleted) {
|
|
62
|
+
this.logger.success('Credentials removed successfully.');
|
|
63
|
+
} else {
|
|
64
|
+
this.logger.warn('No credentials file found to remove.');
|
|
65
|
+
}
|
|
66
|
+
} catch (error) {
|
|
67
|
+
if (this.isUserCancelled(error)) {
|
|
68
|
+
this.logger.warn('Logout cancelled.');
|
|
69
|
+
process.exit(0);
|
|
70
|
+
}
|
|
71
|
+
this.exitWithError(this.formatError(error));
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export const logoutCommand = new LogoutCommand().getCommand();
|