Skip to content

Commit 730aa0a

Browse files
BarbatosBarbatos
authored andcommitted
feat(plugins): add keystore list and update commands, deprecate --keystore-factory
Add remaining keystore subcommands to Toolkit.jar: - `keystore list`: display all keystore files and addresses in a directory - `keystore update <address>`: re-encrypt a keystore with a new password Both support --keystore-dir, --json, and --password-file options. Add deprecation warning to --keystore-factory in FullNode.jar, directing users to the new Toolkit.jar keystore commands. The old REPL continues to function normally during the transition period.
1 parent 919bef6 commit 730aa0a

14 files changed

Lines changed: 675 additions & 1 deletion

File tree

crypto/src/test/java/org/tron/keystore/CrossImplTest.java

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,12 @@
77
import com.fasterxml.jackson.databind.DeserializationFeature;
88
import com.fasterxml.jackson.databind.ObjectMapper;
99
import java.io.File;
10+
import java.io.InputStream;
11+
import java.util.Arrays;
1012
import org.junit.Test;
1113
import org.tron.common.crypto.SignInterface;
1214
import org.tron.common.crypto.SignUtils;
15+
import org.tron.common.utils.ByteArray;
1316
import org.tron.common.utils.Utils;
1417

1518
/**
@@ -25,6 +28,56 @@ public class CrossImplTest {
2528
private static final ObjectMapper MAPPER = new ObjectMapper()
2629
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
2730

31+
// --- web3j standard test vectors (known password + private key) ---
32+
33+
private static final String WEB3J_PASSWORD = "Insecure Pa55w0rd";
34+
private static final String WEB3J_SECRET =
35+
"a392604efc2fad9c0b3da43b5f698a2e3f270f170d859912be0d54742275c5f6";
36+
37+
@Test
38+
public void testDecryptWeb3jPbkdf2Fixture() throws Exception {
39+
WalletFile walletFile = loadFixture("keystore-fixtures/web3j-pbkdf2.json");
40+
SignInterface recovered = Wallet.decrypt(WEB3J_PASSWORD, walletFile, true);
41+
assertEquals("Private key must match web3j test vector",
42+
WEB3J_SECRET, ByteArray.toHexString(recovered.getPrivateKey()));
43+
}
44+
45+
@Test
46+
public void testDecryptWeb3jScryptFixture() throws Exception {
47+
WalletFile walletFile = loadFixture("keystore-fixtures/web3j-scrypt.json");
48+
SignInterface recovered = Wallet.decrypt(WEB3J_PASSWORD, walletFile, true);
49+
assertEquals("Private key must match web3j test vector",
50+
WEB3J_SECRET, ByteArray.toHexString(recovered.getPrivateKey()));
51+
}
52+
53+
// --- geth-generated fixtures (can decrypt, address matches) ---
54+
55+
@Test
56+
public void testDecryptGethStandardScrypt() throws Exception {
57+
WalletFile walletFile = loadFixture("keystore-fixtures/geth-scrypt-standard.json");
58+
SignInterface recovered = Wallet.decrypt("testpassword123", walletFile, true);
59+
assertNotNull("Must decrypt geth standard scrypt keystore", recovered);
60+
assertNotNull("Recovered key must not be null", recovered.getPrivateKey());
61+
// Geth stores 20-byte Ethereum address, TRON uses 21-byte (0x41 prefix).
62+
byte[] tronAddr = recovered.getAddress();
63+
String ethAddr = ByteArray.toHexString(Arrays.copyOfRange(tronAddr, 1, tronAddr.length));
64+
assertEquals("Recovered address must match geth keystore address",
65+
walletFile.getAddress(), ethAddr);
66+
}
67+
68+
@Test
69+
public void testDecryptGethLightScrypt() throws Exception {
70+
WalletFile walletFile = loadFixture("keystore-fixtures/geth-scrypt-light.json");
71+
SignInterface recovered = Wallet.decrypt("lightkdfpass456", walletFile, true);
72+
assertNotNull("Must decrypt geth light scrypt keystore", recovered);
73+
byte[] tronAddr = recovered.getAddress();
74+
String ethAddr = ByteArray.toHexString(Arrays.copyOfRange(tronAddr, 1, tronAddr.length));
75+
assertEquals("Recovered address must match geth keystore address",
76+
walletFile.getAddress(), ethAddr);
77+
}
78+
79+
// --- java-tron self roundtrip ---
80+
2881
@Test
2982
public void testLightKeystoreRoundtrip() throws Exception {
3083
String password = "testpassword123";
@@ -111,4 +164,10 @@ public void testLoadCredentialsIntegration() throws Exception {
111164
tempDir.delete();
112165
}
113166
}
167+
168+
private WalletFile loadFixture(String resourcePath) throws Exception {
169+
InputStream is = getClass().getClassLoader().getResourceAsStream(resourcePath);
170+
assertNotNull("Fixture not found: " + resourcePath, is);
171+
return MAPPER.readValue(is, WalletFile.class);
172+
}
114173
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"address":"0591c156bc7a2c6721dab295b297c70dfcaf9cea","crypto":{"cipher":"aes-128-ctr","ciphertext":"0bff085a14b71b375d6870dfe6ddb1297c0be9decf7e2dca518000485edda2d8","cipherparams":{"iv":"7185a287f8062c077ad6d296f3447045"},"kdf":"scrypt","kdfparams":{"dklen":32,"n":4096,"p":6,"r":8,"salt":"8ddcff7ceb9b58bf2539bc9a72102ca5dbc6e2ffd2230ee9f7656700ecba8d42"},"mac":"dc8e83351d90fff0b7871b828679b3e56fdd5d544c9ec0d40dcb0c227a187f2b"},"id":"0eb9a704-4fb0-41b6-8a2d-50d82d9d22d9","version":3}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"address":"df0b7c3dfbc67776d47b61780fc5fba60072db74","crypto":{"cipher":"aes-128-ctr","ciphertext":"22d21d1b9e93e947f8f58f16ae9992e5230eec3e7e92093c848ef4c6bb7450b7","cipherparams":{"iv":"541f95abfab2037ebd22ef546aa1226c"},"kdf":"scrypt","kdfparams":{"dklen":32,"n":262144,"p":1,"r":8,"salt":"2846f8bd29a38dd7a43d44ad07eb0f4b9e526a37737600a60495cca045883e2b"},"mac":"bbe9a1eebf91f503fe0515e8dae1a9be68f9ecdfa9669a48afaf12c5a4abf9e3"},"id":"3cc4db6a-b837-45a6-95e0-f9bf7d79cd19","version":3}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
{
2+
"crypto" : {
3+
"cipher" : "aes-128-ctr",
4+
"cipherparams" : {
5+
"iv" : "02ebc768684e5576900376114625ee6f"
6+
},
7+
"ciphertext" : "7ad5c9dd2c95f34a92ebb86740b92103a5d1cc4c2eabf3b9a59e1f83f3181216",
8+
"kdf" : "pbkdf2",
9+
"kdfparams" : {
10+
"c" : 262144,
11+
"dklen" : 32,
12+
"prf" : "hmac-sha256",
13+
"salt" : "0e4cf3893b25bb81efaae565728b5b7cde6a84e224cbf9aed3d69a31c981b702"
14+
},
15+
"mac" : "2b29e4641ec17f4dc8b86fc8592090b50109b372529c30b001d4d96249edaf62"
16+
},
17+
"id" : "af0451b4-6020-4ef0-91ec-794a5a965b01",
18+
"version" : 3
19+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
{
2+
"crypto" : {
3+
"cipher" : "aes-128-ctr",
4+
"cipherparams" : {
5+
"iv" : "3021e1ef4774dfc5b08307f3a4c8df00"
6+
},
7+
"ciphertext" : "4dd29ba18478b98cf07a8a44167acdf7e04de59777c4b9c139e3d3fa5cb0b931",
8+
"kdf" : "scrypt",
9+
"kdfparams" : {
10+
"dklen" : 32,
11+
"n" : 262144,
12+
"r" : 8,
13+
"p" : 1,
14+
"salt" : "4f9f68c71989eb3887cd947c80b9555fce528f210199d35c35279beb8c2da5ca"
15+
},
16+
"mac" : "7e8f2192767af9be18e7a373c1986d9190fcaa43ad689bbb01a62dbde159338d"
17+
},
18+
"id" : "7654525c-17e0-4df5-94b5-c7fde752c9d2",
19+
"version" : 3
20+
}

framework/src/main/java/org/tron/program/KeystoreFactory.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,14 @@ public class KeystoreFactory {
2020
private static final String FilePath = "Wallet";
2121

2222
public static void start() {
23+
System.err.println("WARNING: --keystore-factory is deprecated and will be removed "
24+
+ "in a future release.");
25+
System.err.println("Please use: java -jar Toolkit.jar keystore <command>");
26+
System.err.println(" keystore new - Generate a new keystore");
27+
System.err.println(" keystore import - Import a private key");
28+
System.err.println(" keystore list - List keystores");
29+
System.err.println(" keystore update - Change password");
30+
System.err.println();
2331
KeystoreFactory cli = new KeystoreFactory();
2432
cli.run();
2533
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package org.tron.program;
2+
3+
import static org.junit.Assert.assertTrue;
4+
5+
import java.io.ByteArrayOutputStream;
6+
import java.io.PrintStream;
7+
import java.nio.charset.StandardCharsets;
8+
import org.junit.Test;
9+
10+
/**
11+
* Verifies that --keystore-factory prints deprecation warning to stderr.
12+
*/
13+
public class KeystoreFactoryDeprecationTest {
14+
15+
@Test
16+
public void testDeprecationWarningPrinted() throws Exception {
17+
PrintStream originalErr = System.err;
18+
PrintStream originalIn = System.out;
19+
ByteArrayOutputStream errContent = new ByteArrayOutputStream();
20+
System.setErr(new PrintStream(errContent));
21+
22+
// Provide "exit" via stdin so the REPL terminates immediately
23+
System.setIn(new java.io.ByteArrayInputStream("exit\n".getBytes()));
24+
try {
25+
KeystoreFactory.start();
26+
} finally {
27+
System.setErr(originalErr);
28+
System.setIn(System.in);
29+
}
30+
31+
String errOutput = errContent.toString("UTF-8");
32+
assertTrue("Should contain deprecation warning",
33+
errOutput.contains("--keystore-factory is deprecated"));
34+
assertTrue("Should point to Toolkit.jar",
35+
errOutput.contains("Toolkit.jar keystore"));
36+
}
37+
}

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@
99
description = "Manage keystore files for witness account keys.",
1010
subcommands = {CommandLine.HelpCommand.class,
1111
KeystoreNew.class,
12-
KeystoreImport.class
12+
KeystoreImport.class,
13+
KeystoreList.class,
14+
KeystoreUpdate.class
1315
},
1416
commandListHeading = "%nCommands:%n%nThe most commonly used keystore commands are:%n"
1517
)
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
package org.tron.plugins;
2+
3+
import com.fasterxml.jackson.databind.DeserializationFeature;
4+
import com.fasterxml.jackson.databind.ObjectMapper;
5+
import java.io.File;
6+
import java.util.concurrent.Callable;
7+
import org.tron.keystore.WalletFile;
8+
import picocli.CommandLine.Command;
9+
import picocli.CommandLine.Option;
10+
11+
@Command(name = "list",
12+
mixinStandardHelpOptions = true,
13+
description = "List all keystore files in a directory.")
14+
public class KeystoreList implements Callable<Integer> {
15+
16+
private static final ObjectMapper MAPPER = new ObjectMapper()
17+
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
18+
19+
@Option(names = {"--keystore-dir"},
20+
description = "Keystore directory (default: ./Wallet)",
21+
defaultValue = "Wallet")
22+
private File keystoreDir;
23+
24+
@Option(names = {"--json"},
25+
description = "Output in JSON format")
26+
private boolean json;
27+
28+
@Override
29+
public Integer call() {
30+
if (!keystoreDir.exists() || !keystoreDir.isDirectory()) {
31+
if (json) {
32+
System.out.println("{\"keystores\":[]}");
33+
} else {
34+
System.out.println("No keystores found in: " + keystoreDir.getAbsolutePath());
35+
}
36+
return 0;
37+
}
38+
39+
File[] files = keystoreDir.listFiles((dir, name) -> name.endsWith(".json"));
40+
if (files == null || files.length == 0) {
41+
if (json) {
42+
System.out.println("{\"keystores\":[]}");
43+
} else {
44+
System.out.println("No keystores found in: " + keystoreDir.getAbsolutePath());
45+
}
46+
return 0;
47+
}
48+
49+
if (json) {
50+
System.out.print("{\"keystores\":[");
51+
}
52+
53+
int count = 0;
54+
for (File file : files) {
55+
try {
56+
WalletFile walletFile = MAPPER.readValue(file, WalletFile.class);
57+
if (walletFile.getAddress() == null || walletFile.getCrypto() == null) {
58+
continue;
59+
}
60+
if (json) {
61+
if (count > 0) {
62+
System.out.print(",");
63+
}
64+
System.out.printf("{\"address\":\"%s\",\"file\":\"%s\"}",
65+
walletFile.getAddress(), file.getName());
66+
} else {
67+
System.out.printf("%-45s %s%n", walletFile.getAddress(), file.getName());
68+
}
69+
count++;
70+
} catch (Exception e) {
71+
// Skip files that aren't valid keystore JSON
72+
}
73+
}
74+
75+
if (json) {
76+
System.out.println("]}");
77+
}
78+
79+
if (!json && count == 0) {
80+
System.out.println("No valid keystores found in: " + keystoreDir.getAbsolutePath());
81+
}
82+
return 0;
83+
}
84+
}

0 commit comments

Comments
 (0)