Skip to content

Commit e51aed1

Browse files
authored
Merge pull request #938 from tronprotocol/add_keystore
add keystore
2 parents 649a732 + d1d45c2 commit e51aed1

File tree

12 files changed

+1237
-14
lines changed

12 files changed

+1237
-14
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,3 +51,4 @@ src/main/resources/META-INF/
5151
/output_witness/
5252
output*
5353
nodeId.properties
54+
Wallet

build.gradle

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -286,8 +286,8 @@ def binaryRelease(taskName, jarName, mainClass) {
286286
}
287287
}
288288

289-
290289
artifacts {
291290
archives(binaryRelease('buildSolidityNodeJar', 'SolidityNode', 'org.tron.program.SolidityNode'),
292-
binaryRelease('buildFullNodeJar', 'FullNode', 'org.tron.program.FullNode'))
293-
}
291+
binaryRelease('buildFullNodeJar', 'FullNode', 'org.tron.program.FullNode'),
292+
binaryRelease('buildKeystoreFactoryJar', 'KeystoreFactory', 'org.tron.program.KeystoreFactory'))
293+
}

src/main/java/org/tron/core/config/args/Args.java

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,10 +36,14 @@
3636
import org.tron.common.crypto.ECKey;
3737
import org.tron.common.overlay.discover.Node;
3838
import org.tron.core.Constant;
39+
import org.tron.common.utils.ByteArray;
3940
import org.tron.core.Wallet;
4041
import org.tron.core.config.Configuration;
4142
import org.tron.core.config.Parameter.ChainConstant;
4243
import org.tron.core.db.AccountStore;
44+
import org.tron.keystore.CipherException;
45+
import org.tron.keystore.Credentials;
46+
import org.tron.keystore.WalletUtils;
4347

4448
@Slf4j
4549
@NoArgsConstructor
@@ -280,7 +284,6 @@ public static void setParam(final String[] args, final String confFileName) {
280284
INSTANCE.setLocalWitnesses(new LocalWitnesses(INSTANCE.privateKey));
281285
logger.debug("Got privateKey from cmd");
282286
} else if (config.hasPath("localwitness")) {
283-
284287
INSTANCE.localWitnesses = new LocalWitnesses();
285288
List<String> localwitness = config.getStringList("localwitness");
286289
if (localwitness.size() > 1) {
@@ -289,6 +292,30 @@ public static void setParam(final String[] args, final String confFileName) {
289292
}
290293
INSTANCE.localWitnesses.setPrivateKeys(localwitness);
291294
logger.debug("Got privateKey from config.conf");
295+
} else if (config.hasPath("localwitnesskeystore")) {
296+
INSTANCE.localWitnesses = new LocalWitnesses();
297+
List<String> privateKeys = new ArrayList<String>();
298+
if (INSTANCE.isWitness()) {
299+
List<String> localwitness = config.getStringList("localwitnesskeystore");
300+
if (localwitness.size() > 0) {
301+
String fileName = System.getProperty("user.dir") + "/" + localwitness.get(0);
302+
System.out.println("Please input your password.");
303+
String password = WalletUtils.inputPassword();
304+
try {
305+
Credentials credentials = WalletUtils
306+
.loadCredentials(password, new File(fileName));
307+
ECKey ecKeyPair = credentials.getEcKeyPair();
308+
String prikey = ByteArray.toHexString(ecKeyPair.getPrivKeyBytes());
309+
privateKeys.add(prikey);
310+
} catch (IOException e) {
311+
logger.warn(e.getMessage());
312+
} catch (CipherException e) {
313+
logger.warn(e.getMessage());
314+
}
315+
}
316+
}
317+
INSTANCE.localWitnesses.setPrivateKeys(privateKeys);
318+
logger.debug("Got privateKey from keystore");
292319
}
293320

294321
if (INSTANCE.isWitness() && CollectionUtils.isEmpty(INSTANCE.localWitnesses.getPrivateKeys())) {
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package org.tron.keystore;
2+
3+
/**
4+
* Cipher exception wrapper.
5+
*/
6+
public class CipherException extends Exception {
7+
8+
public CipherException(String message) {
9+
super(message);
10+
}
11+
12+
public CipherException(Throwable cause) {
13+
super(cause);
14+
}
15+
16+
public CipherException(String message, Throwable cause) {
17+
super(message, cause);
18+
}
19+
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
package org.tron.keystore;
2+
3+
import org.tron.common.crypto.ECKey;
4+
import org.tron.common.utils.ByteArray;
5+
6+
/**
7+
* Credentials wrapper.
8+
*/
9+
public class Credentials {
10+
11+
private final ECKey ecKeyPair;
12+
private final String address;
13+
14+
private Credentials(ECKey ecKeyPair, String address) {
15+
this.ecKeyPair = ecKeyPair;
16+
this.address = address;
17+
}
18+
19+
public ECKey getEcKeyPair() {
20+
return ecKeyPair;
21+
}
22+
23+
public String getAddress() {
24+
return address;
25+
}
26+
27+
public static Credentials create(ECKey ecKeyPair) {
28+
String address = org.tron.core.Wallet.encode58Check(ecKeyPair.getAddress());
29+
return new Credentials(ecKeyPair, address);
30+
}
31+
32+
public static Credentials create(String privateKey) {
33+
ECKey eCkey = ECKey.fromPrivate(ByteArray.fromHexString(privateKey));
34+
return create(eCkey);
35+
}
36+
37+
@Override
38+
public boolean equals(Object o) {
39+
if (this == o) {
40+
return true;
41+
}
42+
if (o == null || getClass() != o.getClass()) {
43+
return false;
44+
}
45+
46+
Credentials that = (Credentials) o;
47+
48+
if (ecKeyPair != null ? !ecKeyPair.equals(that.ecKeyPair) : that.ecKeyPair != null) {
49+
return false;
50+
}
51+
52+
return address != null ? address.equals(that.address) : that.address == null;
53+
}
54+
55+
@Override
56+
public int hashCode() {
57+
int result = ecKeyPair != null ? ecKeyPair.hashCode() : 0;
58+
result = 31 * result + (address != null ? address.hashCode() : 0);
59+
return result;
60+
}
61+
}
Lines changed: 239 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,239 @@
1+
package org.tron.keystore;
2+
3+
import static java.nio.charset.StandardCharsets.UTF_8;
4+
5+
import java.security.InvalidAlgorithmParameterException;
6+
import java.security.InvalidKeyException;
7+
import java.security.NoSuchAlgorithmException;
8+
import java.security.SecureRandom;
9+
import java.util.Arrays;
10+
import java.util.UUID;
11+
import javax.crypto.BadPaddingException;
12+
import javax.crypto.Cipher;
13+
import javax.crypto.IllegalBlockSizeException;
14+
import javax.crypto.NoSuchPaddingException;
15+
import javax.crypto.spec.IvParameterSpec;
16+
import javax.crypto.spec.SecretKeySpec;
17+
import org.spongycastle.crypto.digests.SHA256Digest;
18+
import org.spongycastle.crypto.generators.PKCS5S2ParametersGenerator;
19+
import org.spongycastle.crypto.generators.SCrypt;
20+
import org.spongycastle.crypto.params.KeyParameter;
21+
import org.tron.common.crypto.ECKey;
22+
import org.tron.common.crypto.Hash;
23+
import org.tron.common.utils.ByteArray;
24+
25+
/**
26+
* <p>Ethereum wallet file management. For reference, refer to <a href="https://github.com/ethereum/wiki/wiki/Web3-Secret-Storage-Definition">
27+
* Web3 Secret Storage Definition</a> or the <a href="https://github.com/ethereum/go-ethereum/blob/master/accounts/key_store_passphrase.go">
28+
* Go Ethereum client implementation</a>.</p>
29+
*
30+
* <p><strong>Note:</strong> the Bouncy Castle Scrypt implementation {@link SCrypt}, fails to comply
31+
* with the following Ethereum reference <a href="https://github.com/ethereum/wiki/wiki/Web3-Secret-Storage-Definition#scrypt">
32+
* Scrypt test vector</a>:</p>
33+
*
34+
* <pre>
35+
* {@code
36+
* // Only value of r that cost (as an int) could be exceeded for is 1
37+
* if (r == 1 && N_STANDARD > 65536)
38+
* {
39+
* throw new IllegalArgumentException("Cost parameter N_STANDARD must be > 1 and < 65536.");
40+
* }
41+
* }
42+
* </pre>
43+
*/
44+
public class Wallet {
45+
46+
private static final int N_LIGHT = 1 << 12;
47+
private static final int P_LIGHT = 6;
48+
49+
private static final int N_STANDARD = 1 << 18;
50+
private static final int P_STANDARD = 1;
51+
52+
private static final int R = 8;
53+
private static final int DKLEN = 32;
54+
55+
private static final int CURRENT_VERSION = 3;
56+
57+
private static final String CIPHER = "aes-128-ctr";
58+
protected static final String AES_128_CTR = "pbkdf2";
59+
protected static final String SCRYPT = "scrypt";
60+
61+
public static WalletFile create(String password, ECKey ecKeyPair, int n, int p)
62+
throws CipherException {
63+
64+
byte[] salt = generateRandomBytes(32);
65+
66+
byte[] derivedKey = generateDerivedScryptKey(password.getBytes(UTF_8), salt, n, R, p, DKLEN);
67+
68+
byte[] encryptKey = Arrays.copyOfRange(derivedKey, 0, 16);
69+
byte[] iv = generateRandomBytes(16);
70+
71+
byte[] privateKeyBytes = ecKeyPair.getPrivKeyBytes();
72+
73+
byte[] cipherText = performCipherOperation(Cipher.ENCRYPT_MODE, iv, encryptKey,
74+
privateKeyBytes);
75+
76+
byte[] mac = generateMac(derivedKey, cipherText);
77+
78+
return createWalletFile(ecKeyPair, cipherText, iv, salt, mac, n, p);
79+
}
80+
81+
public static WalletFile createStandard(String password, ECKey ecKeyPair)
82+
throws CipherException {
83+
return create(password, ecKeyPair, N_STANDARD, P_STANDARD);
84+
}
85+
86+
public static WalletFile createLight(String password, ECKey ecKeyPair)
87+
throws CipherException {
88+
return create(password, ecKeyPair, N_LIGHT, P_LIGHT);
89+
}
90+
91+
private static WalletFile createWalletFile(
92+
ECKey ecKeyPair, byte[] cipherText, byte[] iv, byte[] salt, byte[] mac,
93+
int n, int p) {
94+
95+
WalletFile walletFile = new WalletFile();
96+
walletFile.setAddress(org.tron.core.Wallet.encode58Check(ecKeyPair.getAddress()));
97+
98+
WalletFile.Crypto crypto = new WalletFile.Crypto();
99+
crypto.setCipher(CIPHER);
100+
crypto.setCiphertext(ByteArray.toHexString(cipherText));
101+
walletFile.setCrypto(crypto);
102+
103+
WalletFile.CipherParams cipherParams = new WalletFile.CipherParams();
104+
cipherParams.setIv(ByteArray.toHexString(iv));
105+
crypto.setCipherparams(cipherParams);
106+
107+
crypto.setKdf(SCRYPT);
108+
WalletFile.ScryptKdfParams kdfParams = new WalletFile.ScryptKdfParams();
109+
kdfParams.setDklen(DKLEN);
110+
kdfParams.setN(n);
111+
kdfParams.setP(p);
112+
kdfParams.setR(R);
113+
kdfParams.setSalt(ByteArray.toHexString(salt));
114+
crypto.setKdfparams(kdfParams);
115+
116+
crypto.setMac(ByteArray.toHexString(mac));
117+
walletFile.setCrypto(crypto);
118+
walletFile.setId(UUID.randomUUID().toString());
119+
walletFile.setVersion(CURRENT_VERSION);
120+
121+
return walletFile;
122+
}
123+
124+
private static byte[] generateDerivedScryptKey(
125+
byte[] password, byte[] salt, int n, int r, int p, int dkLen) throws CipherException {
126+
return SCrypt.generate(password, salt, n, r, p, dkLen);
127+
}
128+
129+
private static byte[] generateAes128CtrDerivedKey(
130+
byte[] password, byte[] salt, int c, String prf) throws CipherException {
131+
132+
if (!prf.equals("hmac-sha256")) {
133+
throw new CipherException("Unsupported prf:" + prf);
134+
}
135+
136+
// Java 8 supports this, but you have to convert the password to a character array, see
137+
// http://stackoverflow.com/a/27928435/3211687
138+
139+
PKCS5S2ParametersGenerator gen = new PKCS5S2ParametersGenerator(new SHA256Digest());
140+
gen.init(password, salt, c);
141+
return ((KeyParameter) gen.generateDerivedParameters(256)).getKey();
142+
}
143+
144+
private static byte[] performCipherOperation(
145+
int mode, byte[] iv, byte[] encryptKey, byte[] text) throws CipherException {
146+
147+
try {
148+
IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);
149+
Cipher cipher = Cipher.getInstance("AES/CTR/NoPadding");
150+
151+
SecretKeySpec secretKeySpec = new SecretKeySpec(encryptKey, "AES");
152+
cipher.init(mode, secretKeySpec, ivParameterSpec);
153+
return cipher.doFinal(text);
154+
} catch (NoSuchPaddingException | NoSuchAlgorithmException
155+
| InvalidAlgorithmParameterException | InvalidKeyException
156+
| BadPaddingException | IllegalBlockSizeException e) {
157+
throw new CipherException("Error performing cipher operation", e);
158+
}
159+
}
160+
161+
private static byte[] generateMac(byte[] derivedKey, byte[] cipherText) {
162+
byte[] result = new byte[16 + cipherText.length];
163+
164+
System.arraycopy(derivedKey, 16, result, 0, 16);
165+
System.arraycopy(cipherText, 0, result, 16, cipherText.length);
166+
167+
return Hash.sha3(result);
168+
}
169+
170+
public static ECKey decrypt(String password, WalletFile walletFile)
171+
throws CipherException {
172+
173+
validate(walletFile);
174+
175+
WalletFile.Crypto crypto = walletFile.getCrypto();
176+
177+
byte[] mac = ByteArray.fromHexString(crypto.getMac());
178+
byte[] iv = ByteArray.fromHexString(crypto.getCipherparams().getIv());
179+
byte[] cipherText = ByteArray.fromHexString(crypto.getCiphertext());
180+
181+
byte[] derivedKey;
182+
183+
WalletFile.KdfParams kdfParams = crypto.getKdfparams();
184+
if (kdfParams instanceof WalletFile.ScryptKdfParams) {
185+
WalletFile.ScryptKdfParams scryptKdfParams =
186+
(WalletFile.ScryptKdfParams) crypto.getKdfparams();
187+
int dklen = scryptKdfParams.getDklen();
188+
int n = scryptKdfParams.getN();
189+
int p = scryptKdfParams.getP();
190+
int r = scryptKdfParams.getR();
191+
byte[] salt = ByteArray.fromHexString(scryptKdfParams.getSalt());
192+
derivedKey = generateDerivedScryptKey(password.getBytes(UTF_8), salt, n, r, p, dklen);
193+
} else if (kdfParams instanceof WalletFile.Aes128CtrKdfParams) {
194+
WalletFile.Aes128CtrKdfParams aes128CtrKdfParams =
195+
(WalletFile.Aes128CtrKdfParams) crypto.getKdfparams();
196+
int c = aes128CtrKdfParams.getC();
197+
String prf = aes128CtrKdfParams.getPrf();
198+
byte[] salt = ByteArray.fromHexString(aes128CtrKdfParams.getSalt());
199+
200+
derivedKey = generateAes128CtrDerivedKey(password.getBytes(UTF_8), salt, c, prf);
201+
} else {
202+
throw new CipherException("Unable to deserialize params: " + crypto.getKdf());
203+
}
204+
205+
byte[] derivedMac = generateMac(derivedKey, cipherText);
206+
207+
if (!Arrays.equals(derivedMac, mac)) {
208+
throw new CipherException("Invalid password provided");
209+
}
210+
211+
byte[] encryptKey = Arrays.copyOfRange(derivedKey, 0, 16);
212+
byte[] privateKey = performCipherOperation(Cipher.DECRYPT_MODE, iv, encryptKey, cipherText);
213+
214+
return ECKey.fromPrivate(privateKey);
215+
216+
}
217+
218+
static void validate(WalletFile walletFile) throws CipherException {
219+
WalletFile.Crypto crypto = walletFile.getCrypto();
220+
221+
if (walletFile.getVersion() != CURRENT_VERSION) {
222+
throw new CipherException("Wallet version is not supported");
223+
}
224+
225+
if (!crypto.getCipher().equals(CIPHER)) {
226+
throw new CipherException("Wallet cipher is not supported");
227+
}
228+
229+
if (!crypto.getKdf().equals(AES_128_CTR) && !crypto.getKdf().equals(SCRYPT)) {
230+
throw new CipherException("KDF type is not supported");
231+
}
232+
}
233+
234+
static byte[] generateRandomBytes(int size) {
235+
byte[] bytes = new byte[size];
236+
new SecureRandom().nextBytes(bytes);
237+
return bytes;
238+
}
239+
}

0 commit comments

Comments
 (0)