Skip to content

Commit ba232f4

Browse files
BarbatosBarbatos
authored andcommitted
style(plugins): use picocli output streams and address review findings
- Replace System.out/err with spec.commandLine().getOut()/getErr() in all keystore commands, consistent with existing db commands pattern - Fix description from "witness account keys" to "account keys" - Unify permission warning for UnsupportedOperationException and IOException - Add Keystore section to plugins/README.md with migration guide - Update tests to use cmd.setOut()/setErr() instead of System.setOut()
1 parent d7f7c6b commit ba232f4

9 files changed

Lines changed: 250 additions & 156 deletions

File tree

plugins/README.md

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,3 +143,72 @@ NOTE: large db may GC overhead limit exceeded.
143143
- `<src>`: Source path for database. Default: output-directory/database
144144
- `--db`: db name.
145145
- `-h | --help`: provide the help info
146+
147+
## Keystore
148+
149+
Keystore provides commands for managing account keystore files (Web3 Secret Storage format).
150+
151+
> **Migrating from `--keystore-factory`**: The legacy `FullNode.jar --keystore-factory` interactive mode is deprecated. Use the Toolkit keystore commands below instead. The mapping is:
152+
> - `GenKeystore``keystore new`
153+
> - `ImportPrivateKey``keystore import`
154+
> - (new) `keystore list` — list all keystores in a directory
155+
> - (new) `keystore update` — change the password of a keystore
156+
157+
### Subcommands
158+
159+
#### keystore new
160+
161+
Generate a new keystore file with a random keypair.
162+
163+
```shell script
164+
# full command
165+
java -jar Toolkit.jar keystore new [-h] [--keystore-dir=<dir>] [--password-file=<file>] [--sm2] [--json]
166+
# examples
167+
java -jar Toolkit.jar keystore new # interactive prompt
168+
java -jar Toolkit.jar keystore new --keystore-dir /data/keystores # custom directory
169+
java -jar Toolkit.jar keystore new --password-file pass.txt --json # non-interactive with JSON output
170+
```
171+
172+
#### keystore import
173+
174+
Import a private key into a new keystore file.
175+
176+
```shell script
177+
# full command
178+
java -jar Toolkit.jar keystore import [-h] [--keystore-dir=<dir>] [--password-file=<file>] [--private-key-file=<file>] [--sm2] [--json]
179+
# examples
180+
java -jar Toolkit.jar keystore import # interactive prompt
181+
java -jar Toolkit.jar keystore import --private-key-file key.txt --json # from file with JSON output
182+
```
183+
184+
#### keystore list
185+
186+
List all keystore files in a directory.
187+
188+
```shell script
189+
# full command
190+
java -jar Toolkit.jar keystore list [-h] [--keystore-dir=<dir>] [--json]
191+
# examples
192+
java -jar Toolkit.jar keystore list # list default ./Wallet directory
193+
java -jar Toolkit.jar keystore list --keystore-dir /data/keystores # custom directory
194+
```
195+
196+
#### keystore update
197+
198+
Change the password of a keystore file.
199+
200+
```shell script
201+
# full command
202+
java -jar Toolkit.jar keystore update [-h] <address> [--keystore-dir=<dir>] [--password-file=<file>] [--new-password-file=<file>] [--json]
203+
# examples
204+
java -jar Toolkit.jar keystore update TXyz...abc # interactive prompt
205+
java -jar Toolkit.jar keystore update TXyz...abc --keystore-dir /data/ks # custom directory
206+
```
207+
208+
### Common Options
209+
210+
- `--keystore-dir`: Keystore directory, default: `./Wallet`.
211+
- `--password-file`: Read password from a file instead of interactive prompt.
212+
- `--sm2`: Use SM2 algorithm instead of ECDSA (for `new` and `import`).
213+
- `--json`: Output in JSON format for scripting.
214+
- `-h | --help`: Provide the help info.

plugins/src/main/java/common/org/tron/plugins/Keystore.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
@Command(name = "keystore",
77
mixinStandardHelpOptions = true,
88
version = "keystore command 1.0",
9-
description = "Manage keystore files for witness account keys.",
9+
description = "Manage keystore files for account keys.",
1010
subcommands = {CommandLine.HelpCommand.class,
1111
KeystoreNew.class,
1212
KeystoreImport.class,

plugins/src/main/java/common/org/tron/plugins/KeystoreCliUtils.java

Lines changed: 28 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import java.io.Console;
55
import java.io.File;
66
import java.io.IOException;
7+
import java.io.PrintWriter;
78
import java.nio.charset.StandardCharsets;
89
import java.nio.file.Files;
910
import java.nio.file.Path;
@@ -28,23 +29,23 @@ final class KeystoreCliUtils {
2829
private KeystoreCliUtils() {
2930
}
3031

31-
static String readPassword(File passwordFile) throws IOException {
32+
static String readPassword(File passwordFile, PrintWriter err) throws IOException {
3233
if (passwordFile != null) {
3334
if (!passwordFile.exists()) {
34-
System.err.println("Password file not found: " + passwordFile.getPath()
35+
err.println("Password file not found: " + passwordFile.getPath()
3536
+ ". Omit --password-file for interactive input.");
3637
return null;
3738
}
3839
if (passwordFile.length() > MAX_FILE_SIZE) {
39-
System.err.println("Password file too large (max 1KB).");
40+
err.println("Password file too large (max 1KB).");
4041
return null;
4142
}
4243
byte[] bytes = Files.readAllBytes(passwordFile.toPath());
4344
try {
4445
String password = stripLineEndings(
4546
new String(bytes, StandardCharsets.UTF_8));
4647
if (!WalletUtils.passwordValid(password)) {
47-
System.err.println("Invalid password: must be at least 6 characters.");
48+
err.println("Invalid password: must be at least 6 characters.");
4849
return null;
4950
}
5051
return password;
@@ -55,30 +56,30 @@ static String readPassword(File passwordFile) throws IOException {
5556

5657
Console console = System.console();
5758
if (console == null) {
58-
System.err.println("No interactive terminal available. "
59+
err.println("No interactive terminal available. "
5960
+ "Use --password-file to provide password.");
6061
return null;
6162
}
6263

6364
char[] pwd1 = console.readPassword("Enter password: ");
6465
if (pwd1 == null) {
65-
System.err.println("Password input cancelled.");
66+
err.println("Password input cancelled.");
6667
return null;
6768
}
6869
char[] pwd2 = console.readPassword("Confirm password: ");
6970
if (pwd2 == null) {
7071
Arrays.fill(pwd1, '\0');
71-
System.err.println("Password input cancelled.");
72+
err.println("Password input cancelled.");
7273
return null;
7374
}
7475
try {
7576
if (!Arrays.equals(pwd1, pwd2)) {
76-
System.err.println("Passwords do not match.");
77+
err.println("Passwords do not match.");
7778
return null;
7879
}
7980
String password = new String(pwd1);
8081
if (!WalletUtils.passwordValid(password)) {
81-
System.err.println("Invalid password: must be at least 6 characters.");
82+
err.println("Invalid password: must be at least 6 characters.");
8283
return null;
8384
}
8485
return password;
@@ -105,11 +106,11 @@ static ObjectMapper mapper() {
105106
return MAPPER;
106107
}
107108

108-
static void printJson(Map<String, String> fields) {
109+
static void printJson(PrintWriter out, PrintWriter err, Map<String, String> fields) {
109110
try {
110-
System.out.println(MAPPER.writeValueAsString(fields));
111+
out.println(MAPPER.writeValueAsString(fields));
111112
} catch (Exception e) {
112-
System.err.println("Error writing JSON output");
113+
err.println("Error writing JSON output");
113114
}
114115
}
115116

@@ -138,40 +139,39 @@ static String stripLineEndings(String s) {
138139
return s.substring(0, end);
139140
}
140141

141-
static boolean checkFileExists(File file, String label) {
142+
static boolean checkFileExists(File file, String label, PrintWriter err) {
142143
if (file != null && !file.exists()) {
143-
System.err.println(label + " not found: " + file.getPath());
144+
err.println(label + " not found: " + file.getPath());
144145
return false;
145146
}
146147
return true;
147148
}
148149

149-
static void printSecurityTips(String address, String fileName) {
150-
System.out.println();
151-
System.out.println("Public address of the key: " + address);
152-
System.out.println("Path of the secret key file: " + fileName);
153-
System.out.println();
154-
System.out.println(
150+
static void printSecurityTips(PrintWriter out, String address, String fileName) {
151+
out.println();
152+
out.println("Public address of the key: " + address);
153+
out.println("Path of the secret key file: " + fileName);
154+
out.println();
155+
out.println(
155156
"- You can share your public address with anyone."
156157
+ " Others need it to interact with you.");
157-
System.out.println(
158+
out.println(
158159
"- You must NEVER share the secret key with anyone!"
159160
+ " The key controls access to your funds!");
160-
System.out.println(
161+
out.println(
161162
"- You must BACKUP your key file!"
162163
+ " Without the key, it's impossible to access account funds!");
163-
System.out.println(
164+
out.println(
164165
"- You must REMEMBER your password!"
165166
+ " Without the password, it's impossible to decrypt the key!");
166167
}
167168

168-
static void setOwnerOnly(File file) {
169+
static void setOwnerOnly(File file, PrintWriter err) {
169170
try {
170171
Files.setPosixFilePermissions(file.toPath(), OWNER_ONLY);
171-
} catch (UnsupportedOperationException e) {
172-
// Windows — skip
173-
} catch (IOException e) {
174-
System.err.println("Warning: could not set file permissions on " + file.getName());
172+
} catch (UnsupportedOperationException | IOException e) {
173+
err.println("Warning: could not set file permissions on " + file.getName()
174+
+ ". Please manually restrict access (e.g. chmod 600).");
175175
}
176176
}
177177
}

plugins/src/main/java/common/org/tron/plugins/KeystoreImport.java

Lines changed: 24 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import java.io.Console;
44
import java.io.File;
55
import java.io.IOException;
6+
import java.io.PrintWriter;
67
import java.nio.charset.StandardCharsets;
78
import java.nio.file.Files;
89
import java.util.Arrays;
@@ -15,13 +16,18 @@
1516
import org.tron.keystore.Credentials;
1617
import org.tron.keystore.WalletUtils;
1718
import picocli.CommandLine.Command;
19+
import picocli.CommandLine.Model.CommandSpec;
1820
import picocli.CommandLine.Option;
21+
import picocli.CommandLine.Spec;
1922

2023
@Command(name = "import",
2124
mixinStandardHelpOptions = true,
2225
description = "Import a private key into a new keystore file.")
2326
public class KeystoreImport implements Callable<Integer> {
2427

28+
@Spec
29+
private CommandSpec spec;
30+
2531
@Option(names = {"--keystore-dir"},
2632
description = "Keystore directory (default: ./Wallet)",
2733
defaultValue = "Wallet")
@@ -49,13 +55,15 @@ public class KeystoreImport implements Callable<Integer> {
4955

5056
@Override
5157
public Integer call() {
58+
PrintWriter out = spec.commandLine().getOut();
59+
PrintWriter err = spec.commandLine().getErr();
5260
try {
53-
if (!KeystoreCliUtils.checkFileExists(keyFile, "Key file")) {
61+
if (!KeystoreCliUtils.checkFileExists(keyFile, "Key file", err)) {
5462
return 1;
5563
}
5664
KeystoreCliUtils.ensureDirectory(keystoreDir);
5765

58-
String privateKey = readPrivateKey();
66+
String privateKey = readPrivateKey(err);
5967
if (privateKey == null) {
6068
return 1;
6169
}
@@ -64,11 +72,11 @@ public Integer call() {
6472
privateKey = privateKey.substring(2);
6573
}
6674
if (!isValidPrivateKey(privateKey)) {
67-
System.err.println("Invalid private key: must be 64 hex characters.");
75+
err.println("Invalid private key: must be 64 hex characters.");
6876
return 1;
6977
}
7078

71-
String password = KeystoreCliUtils.readPassword(passwordFile);
79+
String password = KeystoreCliUtils.readPassword(passwordFile, err);
7280
if (password == null) {
7381
return 1;
7482
}
@@ -79,42 +87,42 @@ public Integer call() {
7987
keyPair = SignUtils.fromPrivate(
8088
ByteArray.fromHexString(privateKey), ecKey);
8189
} catch (Exception e) {
82-
System.err.println("Invalid private key: not a valid key"
90+
err.println("Invalid private key: not a valid key"
8391
+ " for the selected algorithm.");
8492
return 1;
8593
}
8694
String address = Credentials.create(keyPair).getAddress();
8795
String existingFile = findExistingKeystore(keystoreDir, address);
8896
if (existingFile != null && !force) {
89-
System.err.println("Keystore for address " + address
97+
err.println("Keystore for address " + address
9098
+ " already exists: " + existingFile
9199
+ ". Use --force to import anyway.");
92100
return 1;
93101
}
94102
String fileName = WalletUtils.generateWalletFile(password, keyPair, keystoreDir, true);
95-
KeystoreCliUtils.setOwnerOnly(new File(keystoreDir, fileName));
103+
KeystoreCliUtils.setOwnerOnly(new File(keystoreDir, fileName), err);
96104
if (json) {
97-
KeystoreCliUtils.printJson(KeystoreCliUtils.jsonMap(
105+
KeystoreCliUtils.printJson(out, err, KeystoreCliUtils.jsonMap(
98106
"address", address, "file", fileName));
99107
} else {
100-
System.out.println("Imported keystore successfully");
101-
KeystoreCliUtils.printSecurityTips(address,
108+
out.println("Imported keystore successfully");
109+
KeystoreCliUtils.printSecurityTips(out, address,
102110
new File(keystoreDir, fileName).getPath());
103111
}
104112
return 0;
105113
} catch (CipherException e) {
106-
System.err.println("Encryption error: " + e.getMessage());
114+
err.println("Encryption error: " + e.getMessage());
107115
return 1;
108116
} catch (Exception e) {
109-
System.err.println("Error: " + e.getMessage());
117+
err.println("Error: " + e.getMessage());
110118
return 1;
111119
}
112120
}
113121

114-
private String readPrivateKey() throws IOException {
122+
private String readPrivateKey(PrintWriter err) throws IOException {
115123
if (keyFile != null) {
116124
if (keyFile.length() > 1024) {
117-
System.err.println("Key file too large (max 1KB).");
125+
err.println("Key file too large (max 1KB).");
118126
return null;
119127
}
120128
byte[] bytes = Files.readAllBytes(keyFile.toPath());
@@ -127,14 +135,14 @@ private String readPrivateKey() throws IOException {
127135

128136
Console console = System.console();
129137
if (console == null) {
130-
System.err.println("No interactive terminal available. "
138+
err.println("No interactive terminal available. "
131139
+ "Use --key-file to provide private key.");
132140
return null;
133141
}
134142

135143
char[] key = console.readPassword("Enter private key (hex): ");
136144
if (key == null) {
137-
System.err.println("Input cancelled.");
145+
err.println("Input cancelled.");
138146
return null;
139147
}
140148
try {

0 commit comments

Comments
 (0)