From 8b1f513b03331b44e345dd926c809cd6517c71cd Mon Sep 17 00:00:00 2001
From: Galaxy <30950645+GalaxySciTech@users.noreply.github.com>
Date: Fri, 1 May 2026 01:27:02 +0600
Subject: [PATCH] docs: rewrite README with full English integration guide
---
README.md | 276 +++---
.../foundation/utils/MnemonicUtil.java | 102 +-
.../consenlabs/tokencore/wallet/Identity.java | 6 +-
.../tokencore/wallet/WalletManager.java | 928 +++++++++---------
.../wallet/keystore/HDMnemonicKeystore.java | 382 +++----
.../wallet/keystore/V3MnemonicKeystore.java | 172 ++--
.../foundation/utils/MnemonicUtilTest.java | 14 +
.../tokencore/wallet/WalletManagerTest.java | 40 +
8 files changed, 989 insertions(+), 931 deletions(-)
diff --git a/README.md b/README.md
index 781537d..c8538d7 100644
--- a/README.md
+++ b/README.md
@@ -1,66 +1,29 @@
-
Tokencore
-
-
- Multi-chain cryptocurrency wallet core library for Java
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Supported Chains •
- Quick Start •
- Integration •
- Offline Signing •
- Contact
-
+# Tokencore
----
-
-## Introduction
+Tokencore is a Java multi-chain wallet core library for exchange backends, custody systems, and wallet services.
-Tokencore is a lightweight Java library that provides core wallet functionality for multiple blockchains. It handles HD wallet derivation, encrypted keystore management, and offline transaction signing — making it the ideal building block for exchange backends and custodial wallet services.
+## What Tokencore provides
-For a complete exchange wallet backend built on top of Tokencore, see [java-wallet](https://github.com/galaxyscitech/java-wallet).
+- Multi-chain address generation
+- HD wallet derivation and mnemonic workflows
+- Encrypted keystore management
+- Offline transaction signing for major chain families
-## Supported Chains
+Supported chains include:
+- **EVM**: Ethereum
+- **Bitcoin family**: Bitcoin, Litecoin, Dogecoin, Dash, Bitcoin Cash, Bitcoin SV
+- **Others**: TRON, Filecoin, EOS
-| Chain | Token Standards | Features |
-|-------|----------------|----------|
-| **Bitcoin** | BTC, OMNI | UTXO management, SegWit (P2WPKH) |
-| **Ethereum** | ETH, ERC-20 | Offline signing, nonce management |
-| **TRON** | TRX, TRC-20 | Transaction signing |
-| **Bitcoin Cash** | BCH | CashAddr format |
-| **Bitcoin SV** | BSV | Transaction signing |
-| **Litecoin** | LTC | Transaction signing |
-| **Dogecoin** | DOGE | Transaction signing |
-| **Dash** | DASH | Transaction signing |
-| **Filecoin** | FIL | Transaction signing |
+---
## Requirements
-- **Java** 8 or higher
-- **Gradle** 8.5+ (included via wrapper, no manual install needed)
+- Java 8+
+- Gradle wrapper included (`./gradlew`)
+
+---
-## Integration
+## Install
### Gradle
@@ -68,6 +31,7 @@ For a complete exchange wallet backend built on top of Tokencore, see [java-wall
repositories {
maven { url 'https://jitpack.io' }
}
+
dependencies {
implementation 'com.github.galaxyscitech:tokencore:1.3.0'
}
@@ -77,148 +41,144 @@ dependencies {
```xml
-
- jitpack.io
- https://jitpack.io
-
+
+ jitpack.io
+ https://jitpack.io
+
- com.github.galaxyscitech
- tokencore
- 1.3.0
+ com.github.galaxyscitech
+ tokencore
+ 1.3.0
```
-## Quick Start
+---
-### 1. Initialize Keystore & Identity
+## Quick start (runnable)
```java
-WalletManager.storage = new KeystoreStorage() {
- @Override
- public File getKeystoreDir() {
- return new File("/path/to/keystore");
- }
-};
-WalletManager.scanWallets();
+import org.consenlabs.tokencore.foundation.utils.MnemonicUtil;
+import org.consenlabs.tokencore.wallet.*;
+import org.consenlabs.tokencore.wallet.model.*;
-String password = "your_password";
-Identity identity = Identity.getCurrentIdentity();
+import java.io.File;
-if (identity == null) {
- identity = Identity.createIdentity(
- "token", password, "", Network.MAINNET, Metadata.P2WPKH);
-}
-```
+public class QuickStart {
+ public static void main(String[] args) {
+ WalletManager.storage = () -> new File("./keystore");
+ WalletManager.scanWallets();
-### 2. Derive a Wallet
+ String password = "UseAStrongPassword_123";
+ Identity identity = Identity.getCurrentIdentity();
+ if (identity == null) {
+ identity = Identity.createIdentity("main", password, "", Network.MAINNET, Metadata.P2WPKH);
+ }
-```java
-Identity identity = Identity.getCurrentIdentity();
-Wallet wallet = identity.deriveWalletByMnemonics(
- ChainType.BITCOIN, "your_password", MnemonicUtil.randomMnemonicCodes());
-System.out.println(wallet.getAddress());
+ Wallet ethWallet = identity.deriveWalletByMnemonics(
+ ChainType.ETHEREUM,
+ password,
+ MnemonicUtil.randomMnemonicCodes()
+ );
+
+ Wallet btcWallet = identity.deriveWalletByMnemonics(
+ ChainType.BITCOIN,
+ password,
+ MnemonicUtil.randomMnemonicCodes()
+ );
+
+ System.out.println("ETH address: " + ethWallet.getAddress());
+ System.out.println("BTC address: " + btcWallet.getAddress());
+ }
+}
```
-## Offline Signing
+---
-Offline signing creates a digital signature without ever exposing private keys to an online environment.
+## Common usage
-### Bitcoin
+### 1) Import wallet from private key
```java
-// 1. Define transaction parameters
-String toAddress = "33sXfhCBPyHqeVsVthmyYonCBshw5XJZn9";
-int changeIdx = 0;
-long amount = 1000L;
-long fee = 555L;
-
-// 2. Collect UTXOs (from your node or a third-party API)
-ArrayList utxos = new ArrayList<>();
-
-// 3. Build and sign
-BitcoinTransaction bitcoinTransaction = new BitcoinTransaction(
- toAddress, changeIdx, amount, fee, utxos);
-Wallet wallet = WalletManager.findWalletByAddress(
- ChainType.BITCOIN, "33sXfhCBPyHqeVsVthmyYonCBshw5XJZn9");
-TxSignResult txSignResult = bitcoinTransaction.signTransaction(
- String.valueOf(ChainId.BITCOIN_MAINNET), "your_password", wallet);
-System.out.println(txSignResult.getSignedTx());
+Metadata metadata = new Metadata();
+metadata.setChainType(ChainType.ETHEREUM);
+metadata.setSource(Metadata.FROM_PRIVATE);
+metadata.setNetwork(Network.MAINNET);
+
+Wallet wallet = WalletManager.importWalletFromPrivateKey(
+ metadata,
+ "4c0883a69102937d6231471b5dbb6204fe512961708279f14a15c89a7e5a5c3c",
+ "password123",
+ true
+);
```
-### Ethereum
+### 2) Import wallet from mnemonic
```java
-EthereumTransaction tx = new EthereumTransaction(
- BigInteger.ZERO, // nonce
- BigInteger.valueOf(20_000_000_000L), // gasPrice
- BigInteger.valueOf(21_000), // gasLimit
- "0xRecipientAddress", // to
- BigInteger.valueOf(1_000_000_000_000_000_000L), // value (1 ETH)
- "" // data
+Metadata metadata = new Metadata();
+metadata.setChainType(ChainType.DOGECOIN);
+metadata.setSource(Metadata.FROM_MNEMONIC);
+metadata.setNetwork(Network.MAINNET);
+metadata.setSegWit(Metadata.NONE);
+
+Wallet wallet = WalletManager.importWalletFromMnemonic(
+ metadata,
+ "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about",
+ BIP44Util.DOGECOIN_MAINNET_PATH,
+ "password123",
+ true
);
-
-Wallet wallet = WalletManager.findWalletByAddress(
- ChainType.ETHEREUM, "0xYourAddress");
-TxSignResult result = tx.signTransaction(
- String.valueOf(ChainId.ETHEREUM_MAINNET), "your_password", wallet);
-System.out.println(result.getSignedTx());
```
-### TRON
+### 3) Find wallet by mnemonic (BTC-family friendly)
```java
-String from = "TJRabPrwbZy45sbavfcjinPJC18kjpRTv8";
-String to = "TF17BgPaZYbz8oxbjhriubPDsA7ArKoLX3";
-
-TronTransaction transaction = new TronTransaction(from, to, 1L);
-Wallet wallet = WalletManager.findWalletByAddress(ChainType.TRON, from);
-TxSignResult result = transaction.signTransaction(
- "mainnet", "your_password", wallet);
-System.out.println(result.getSignedTx());
+Wallet wallet = WalletManager.findWalletByMnemonic(
+ ChainType.DOGECOIN,
+ Network.MAINNET,
+ "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about",
+ BIP44Util.DOGECOIN_MAINNET_PATH,
+ Metadata.NONE
+);
```
-## Build & Test
-
-```bash
-# Build the library
-./gradlew build
+### 4) Export keystore and recover by keystore
-# Run the test suite
-./gradlew test
+```java
+String keystoreJson = WalletManager.exportKeystore(wallet.getId(), "password123");
+Wallet found = WalletManager.findWalletByKeystore(ChainType.ETHEREUM, keystoreJson, "password123");
```
-## Project Structure
+---
-```
-src/main/java/org/consenlabs/tokencore/
-├── wallet/
-│ ├── Identity.java # HD identity management
-│ ├── Wallet.java # Wallet abstraction
-│ ├── WalletManager.java # Wallet lifecycle & discovery
-│ ├── address/ # Chain-specific address generation
-│ ├── keystore/ # Encrypted keystore implementations
-│ ├── model/ # ChainType, ChainId, Metadata, etc.
-│ ├── network/ # Bitcoin-fork network parameters
-│ ├── transaction/ # Offline signing per chain
-│ └── validators/ # Address & key validation
-└── foundation/
- ├── crypto/ # AES, KDF, hashing primitives
- ├── utils/ # Mnemonic, numeric, byte helpers
- └── rlp/ # RLP encoding (Ethereum)
-```
+## Security recommendations
-## License
+- Never log or print private keys, mnemonics, or decrypted keystore payloads.
+- Keep signing in isolated/offline environments whenever possible.
+- Use strong passwords and avoid hardcoded secrets.
+- Consider HSM/KMS for production secret governance.
+- Enforce strict access controls around keystore files.
-This project is licensed under the [GNU General Public License v3.0](LICENSE).
+---
-## Contact
+## Typical errors
-- **Telegram**: [t.me/GalaxySciTech](https://t.me/GalaxySciTech)
-- **Website**: [galaxy.doctor](https://galaxy.doctor)
-- **GitHub Issues**: [Report a bug](https://github.com/galaxyscitech/tokencore/issues/new)
+- `password_incorrect`
+- `mnemonic_length_invalid`
+- `mnemonic_word_invalid`
+- `invalid_mnemonic_path`
+- `unsupported_chain`
+- `private_key_address_not_match`
---
-> **Disclaimer**: Tokencore is a functional component for digital currency operations. It is intended primarily for learning and development purposes and does not provide a complete blockchain business solution. Use at your own risk.
+## Build and test
+
+```bash
+./gradlew test
+./gradlew build
+```
+
+CI runs on Java 8/11/17 via GitHub Actions.
diff --git a/src/main/java/org/consenlabs/tokencore/foundation/utils/MnemonicUtil.java b/src/main/java/org/consenlabs/tokencore/foundation/utils/MnemonicUtil.java
index b5de055..d9c52a5 100755
--- a/src/main/java/org/consenlabs/tokencore/foundation/utils/MnemonicUtil.java
+++ b/src/main/java/org/consenlabs/tokencore/foundation/utils/MnemonicUtil.java
@@ -1,43 +1,59 @@
-package org.consenlabs.tokencore.foundation.utils;
-
-import com.google.common.base.Joiner;
-import org.bitcoinj.crypto.MnemonicCode;
-import org.consenlabs.tokencore.wallet.model.Messages;
-import org.consenlabs.tokencore.wallet.model.TokenException;
-
-import java.util.List;
-
-public class MnemonicUtil {
- public static void validateMnemonics(List mnemonicCodes) {
- try {
- MnemonicCode.INSTANCE.check(mnemonicCodes);
- } catch (org.bitcoinj.crypto.MnemonicException.MnemonicLengthException e) {
- throw new TokenException(Messages.MNEMONIC_INVALID_LENGTH);
- } catch (org.bitcoinj.crypto.MnemonicException.MnemonicWordException e) {
- throw new TokenException(Messages.MNEMONIC_BAD_WORD);
- } catch (Exception e) {
- throw new TokenException(Messages.MNEMONIC_CHECKSUM);
- }
- }
-
- public static List randomMnemonicCodes() {
- return toMnemonicCodes(NumericUtil.generateRandomBytes(16));
- }
-
- public static String randomMnemonicStr() {
- List mnemonicCodes=randomMnemonicCodes();
- return Joiner.on(" ").join(mnemonicCodes);
- }
-
-
- public static List toMnemonicCodes(byte[] entropy) {
- try {
- return MnemonicCode.INSTANCE.toMnemonic(entropy);
- } catch (org.bitcoinj.crypto.MnemonicException.MnemonicLengthException e) {
- throw new TokenException(Messages.MNEMONIC_INVALID_LENGTH);
- } catch (Exception e) {
- throw new TokenException(Messages.MNEMONIC_CHECKSUM);
- }
- }
-
-}
+package org.consenlabs.tokencore.foundation.utils;
+
+import com.google.common.base.Joiner;
+import org.bitcoinj.crypto.MnemonicCode;
+import org.consenlabs.tokencore.wallet.model.Messages;
+import org.consenlabs.tokencore.wallet.model.TokenException;
+
+import java.util.Arrays;
+import java.util.List;
+
+public class MnemonicUtil {
+ public static void validateMnemonics(List mnemonicCodes) {
+ try {
+ MnemonicCode.INSTANCE.check(mnemonicCodes);
+ } catch (org.bitcoinj.crypto.MnemonicException.MnemonicLengthException e) {
+ throw new TokenException(Messages.MNEMONIC_INVALID_LENGTH);
+ } catch (org.bitcoinj.crypto.MnemonicException.MnemonicWordException e) {
+ throw new TokenException(Messages.MNEMONIC_BAD_WORD);
+ } catch (Exception e) {
+ throw new TokenException(Messages.MNEMONIC_CHECKSUM);
+ }
+ }
+
+ public static List randomMnemonicCodes() {
+ return toMnemonicCodes(NumericUtil.generateRandomBytes(16));
+ }
+
+ public static String randomMnemonicStr() {
+ List mnemonicCodes=randomMnemonicCodes();
+ return Joiner.on(" ").join(mnemonicCodes);
+ }
+
+
+
+
+ public static List toMnemonicCodes(String mnemonic) {
+ if (mnemonic == null) {
+ throw new TokenException(Messages.MNEMONIC_INVALID_LENGTH);
+ }
+
+ String normalized = mnemonic.trim().replaceAll("\\s+", " ");
+ if (normalized.isEmpty()) {
+ throw new TokenException(Messages.MNEMONIC_INVALID_LENGTH);
+ }
+
+ return Arrays.asList(normalized.split(" "));
+ }
+
+ public static List toMnemonicCodes(byte[] entropy) {
+ try {
+ return MnemonicCode.INSTANCE.toMnemonic(entropy);
+ } catch (org.bitcoinj.crypto.MnemonicException.MnemonicLengthException e) {
+ throw new TokenException(Messages.MNEMONIC_INVALID_LENGTH);
+ } catch (Exception e) {
+ throw new TokenException(Messages.MNEMONIC_CHECKSUM);
+ }
+ }
+
+}
diff --git a/src/main/java/org/consenlabs/tokencore/wallet/Identity.java b/src/main/java/org/consenlabs/tokencore/wallet/Identity.java
index 091207e..62f5905 100755
--- a/src/main/java/org/consenlabs/tokencore/wallet/Identity.java
+++ b/src/main/java/org/consenlabs/tokencore/wallet/Identity.java
@@ -97,7 +97,7 @@ public static Identity createIdentity(String name, String password, String passw
public static Identity recoverIdentity(String mnemonic, String name, String password,
String passwordHit, String network, String segWit) {
- List mnemonicCodes = Arrays.asList(mnemonic.split(" "));
+ List mnemonicCodes = MnemonicUtil.toMnemonicCodes(mnemonic);
Metadata metadata = new Metadata();
metadata.setName(name);
metadata.setPasswordHint(passwordHit);
@@ -145,7 +145,7 @@ void removeWallet(String walletId) {
public Wallet deriveWallet(String chainType, String password) {
String mnemonic = exportIdentity(password);
- List mnemonics = Arrays.asList(mnemonic.split(" "));
+ List mnemonics = MnemonicUtil.toMnemonicCodes(mnemonic);
return deriveWalletByMnemonics(chainType,password,mnemonics);
}
@@ -157,7 +157,7 @@ public Wallet deriveWalletByMnemonics(String chainType, String password, List deriveWallets(List chainTypes, String password) {
String mnemonic = exportIdentity(password);
- List mnemonics = Arrays.asList(mnemonic.split(" "));
+ List mnemonics = MnemonicUtil.toMnemonicCodes(mnemonic);
return deriveWalletsByMnemonics(chainTypes, password, mnemonics);
}
diff --git a/src/main/java/org/consenlabs/tokencore/wallet/WalletManager.java b/src/main/java/org/consenlabs/tokencore/wallet/WalletManager.java
index f588ac4..7df8706 100755
--- a/src/main/java/org/consenlabs/tokencore/wallet/WalletManager.java
+++ b/src/main/java/org/consenlabs/tokencore/wallet/WalletManager.java
@@ -1,450 +1,478 @@
-package org.consenlabs.tokencore.wallet;
-
-import com.fasterxml.jackson.annotation.JsonInclude;
-import com.fasterxml.jackson.databind.DeserializationFeature;
-import com.fasterxml.jackson.databind.MapperFeature;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import com.google.common.base.Strings;
-import com.google.common.io.CharSource;
-import com.google.common.io.Files;
-import org.bitcoinj.crypto.DeterministicKey;
-import org.bitcoinj.wallet.DeterministicKeyChain;
-import org.bitcoinj.wallet.DeterministicSeed;
-import org.consenlabs.tokencore.foundation.utils.MnemonicUtil;
-import org.consenlabs.tokencore.foundation.utils.NumericUtil;
-import org.consenlabs.tokencore.wallet.address.AddressCreatorManager;
-import org.consenlabs.tokencore.wallet.address.EthereumAddressCreator;
-import org.consenlabs.tokencore.wallet.keystore.*;
-import org.consenlabs.tokencore.wallet.model.*;
-import org.consenlabs.tokencore.wallet.validators.PrivateKeyValidator;
-import org.json.JSONObject;
-
-import javax.annotation.Nullable;
-import java.io.File;
-import java.io.IOException;
-import java.nio.charset.Charset;
-import java.util.Arrays;
-import java.util.List;
-import java.util.concurrent.ConcurrentHashMap;
-
-public class WalletManager {
- private static final ConcurrentHashMap keystoreMap = new ConcurrentHashMap<>();
-
- private static final ObjectMapper WRITE_MAPPER = new ObjectMapper();
- private static final ObjectMapper READ_MAPPER = new ObjectMapper();
-
- static {
- WRITE_MAPPER.setSerializationInclusion(JsonInclude.Include.NON_NULL);
- READ_MAPPER.configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES, true);
- READ_MAPPER.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
- READ_MAPPER.configure(DeserializationFeature.FAIL_ON_MISSING_CREATOR_PROPERTIES, true);
- }
-
- public static KeystoreStorage storage;
-
- static ObjectMapper getWriteMapper() {
- return WRITE_MAPPER;
- }
-
- static Wallet createWallet(IMTKeystore keystore) {
- File file = generateWalletFile(keystore.getId());
- writeToFile(keystore, file);
- keystoreMap.put(keystore.getId(), keystore);
- return new Wallet(keystore);
- }
-
- public static ConcurrentHashMap getKeyMap(){
- return keystoreMap;
- }
-
- public static void changePassword(String id, String oldPassword, String newPassword) {
- IMTKeystore keystore = mustFindKeystoreById(id);
- IMTKeystore newKeystore = (IMTKeystore) keystore.changePassword(oldPassword, newPassword);
- flushWallet(newKeystore, true);
- }
-
- public static String exportPrivateKey(String id, String password) {
- Wallet wallet = mustFindWalletById(id);
- return wallet.exportPrivateKey(password);
- }
-
- public static List exportPrivateKeys(String id, String password) {
- Wallet wallet = mustFindWalletById(id);
- return wallet.exportPrivateKeys(password);
- }
-
- public static MnemonicAndPath exportMnemonic(String id, String password) {
- Wallet wallet = mustFindWalletById(id);
- return wallet.exportMnemonic(password);
- }
-
- public static String exportKeystore(String id, String password) {
- Wallet wallet = mustFindWalletById(id);
- return wallet.exportKeystore(password);
- }
-
- public static void removeWallet(String id, String password) {
- Wallet wallet = mustFindWalletById(id);
- if (!wallet.verifyPassword(password)) {
- throw new TokenException(Messages.WALLET_INVALID_PASSWORD);
- }
- if (wallet.delete(password)) {
- Identity.getCurrentIdentity().removeWallet(id);
- keystoreMap.remove(id);
- }
- }
-
- public static void clearKeystoreMap() {
- keystoreMap.clear();
- }
-
- public static Wallet importWalletFromKeystore(Metadata metadata, String keystoreContent, String password, boolean overwrite) {
- WalletKeystore importedKeystore = validateKeystore(keystoreContent, password);
-
- if (metadata.getSource() == null)
- metadata.setSource(Metadata.FROM_KEYSTORE);
-
- String privateKey = NumericUtil.bytesToHex(importedKeystore.decryptCiphertext(password));
- try {
- new PrivateKeyValidator(privateKey).validate();
- } catch (TokenException ex) {
- if (Messages.PRIVATE_KEY_INVALID.equals(ex.getMessage())) {
- throw new TokenException(Messages.KEYSTORE_CONTAINS_INVALID_PRIVATE_KEY);
- } else {
- throw ex;
- }
- }
- return importWalletFromPrivateKey(metadata, privateKey, password, overwrite);
- }
-
- public static Wallet importWalletFromPrivateKey(Metadata metadata, String prvKeyHex, String password, boolean overwrite) {
- IMTKeystore keystore = V3Keystore.create(metadata, password, prvKeyHex);
- Wallet wallet = flushWallet(keystore, overwrite);
- Identity.getCurrentIdentity().addWallet(wallet);
- return wallet;
- }
-
- /**
- * Just for import EOS wallet
- */
- public static Wallet importWalletFromPrivateKeys(Metadata metadata, String accountName, List prvKeys, List permissions, String password, boolean overwrite) {
- IMTKeystore keystore = null;
- if (!ChainType.EOS.equalsIgnoreCase(metadata.getChainType())) {
- throw new TokenException("This method is only for importing EOS wallet");
- }
- keystore = EOSKeystore.create(metadata, password, accountName, prvKeys, permissions);
- return persistWallet(keystore, overwrite);
- }
-
- /**
- * use the importWalletFromPrivateKeys
- */
- @Deprecated
- public static Wallet importWalletFromPrivateKey(Metadata metadata, String accountName, String prvKeyHex, String password, boolean overwrite) {
- IMTKeystore keystore = LegacyEOSKeystore.create(metadata, accountName, password, prvKeyHex);
- return persistWallet(keystore, overwrite);
- }
-
-
- /**
- * import wallet from mnemonic
- *
- * @param metadata
- * @param accountName only for EOS
- * @param mnemonic
- * @param path
- * @param permissions only for EOS
- * @param password
- * @param overwrite
- * @return
- */
- public static Wallet importWalletFromMnemonic(Metadata metadata, @Nullable String accountName, String mnemonic, String path, @Nullable List permissions, String password, boolean overwrite) {
-
- if (metadata.getSource() == null)
- metadata.setSource(Metadata.FROM_MNEMONIC);
- IMTKeystore keystore = null;
- List mnemonicCodes = Arrays.asList(mnemonic.split(" "));
- MnemonicUtil.validateMnemonics(mnemonicCodes);
- switch (metadata.getChainType()) {
- case ChainType.ETHEREUM:
- case ChainType.TRON:
- case ChainType.FILECOIN:
- keystore = V3MnemonicKeystore.create(metadata, password, mnemonicCodes, path);
- break;
- case ChainType.BITCOIN:
- case ChainType.LITECOIN:
- case ChainType.DASH:
- case ChainType.DOGECOIN:
- case ChainType.BITCOINCASH:
- case ChainType.BITCOINSV:
- keystore = HDMnemonicKeystore.create(metadata, password, mnemonicCodes, path);
- break;
- case ChainType.EOS:
- keystore = EOSKeystore.create(metadata, password, accountName, mnemonicCodes, path, permissions);
- break;
- default:
- throw new TokenException(String.format("Mnemonic import not supported for chain: %s", metadata.getChainType()));
- }
-
- return persistWallet(keystore, overwrite);
- }
-
- public static Wallet importWalletFromMnemonic(Metadata metadata, String mnemonic, String path, String password, boolean overwrite) {
- return importWalletFromMnemonic(metadata, null, mnemonic, path, null, password, overwrite);
- }
-
- public static Wallet findWalletByPrivateKey(String chainType, String network, String privateKey, String segWit) {
- if (ChainType.ETHEREUM.equals(chainType)) {
- new PrivateKeyValidator(privateKey).validate();
- }
- Network net = new Network(network);
- String address = AddressCreatorManager.getInstance(chainType, net.isMainnet(), segWit).fromPrivateKey(privateKey);
- return findWalletByAddress(chainType, address);
- }
-
- public static Wallet findWalletByKeystore(String chainType, String keystoreContent, String password) {
- WalletKeystore walletKeystore = validateKeystore(keystoreContent, password);
-
- byte[] prvKeyBytes = walletKeystore.decryptCiphertext(password);
- String address = new EthereumAddressCreator().fromPrivateKey(prvKeyBytes);
- return findWalletByAddress(chainType, address);
- }
-
- public static Wallet findWalletByMnemonic(String chainType, String network, String mnemonic, String path, String segWit) {
- List mnemonicCodes = Arrays.asList(mnemonic.split(" "));
- MnemonicUtil.validateMnemonics(mnemonicCodes);
- DeterministicSeed seed = new DeterministicSeed(mnemonicCodes, null, "", 0L);
- DeterministicKeyChain keyChain = DeterministicKeyChain.builder().seed(seed).build();
- if (Strings.isNullOrEmpty(path)) {
- throw new TokenException(Messages.INVALID_MNEMONIC_PATH);
- }
-
- if (ChainType.BITCOIN.equalsIgnoreCase(chainType)||ChainType.LITECOIN.equalsIgnoreCase(chainType)) {
- path += "/0/0";
- }
-
- DeterministicKey key = keyChain.getKeyByPath(BIP44Util.generatePath(path), true);
- Network net = new Network(network);
- String address = AddressCreatorManager.getInstance(chainType, net.isMainnet(), segWit).fromPrivateKey(key.getPrivateKeyAsHex());
- return findWalletByAddress(chainType, address);
- }
-
- public static Wallet switchBTCWalletMode(String id, String password, String model) {
- Wallet wallet = mustFindWalletById(id);
- // !!! Warning !!! You must verify password before you write content to keystore
- if (!wallet.getMetadata().getChainType().equalsIgnoreCase(ChainType.BITCOIN))
- throw new TokenException("Ethereum wallet can't switch mode");
- Metadata metadata = wallet.getMetadata().clone();
- if (metadata.getSegWit().equalsIgnoreCase(model)) {
- return wallet;
- }
-
- metadata.setSegWit(model);
- IMTKeystore keystore;
- if (wallet.hasMnemonic()) {
-
- MnemonicAndPath mnemonicAndPath = wallet.exportMnemonic(password);
- String path = BIP44Util.getBTCMnemonicPath(model, metadata.isMainNet());
- List mnemonicCodes = Arrays.asList(mnemonicAndPath.getMnemonic().split(" "));
- keystore = new HDMnemonicKeystore(metadata, password, mnemonicCodes, path, wallet.getId());
- } else {
- String prvKey = wallet.exportPrivateKey(password);
- keystore = new V3Keystore(metadata, password, prvKey, wallet.getId());
-
- }
- flushWallet(keystore, false);
- keystoreMap.put(wallet.getId(), keystore);
- return new Wallet(keystore);
- }
-
- public static Wallet setAccountName(String id, String accountName) {
- Wallet wallet = mustFindWalletById(id);
- wallet.setAccountName(accountName);
- return persistWallet(wallet.getKeystore(), true);
- }
-
- static Wallet findWalletById(String id) {
- IMTKeystore keystore = keystoreMap.get(id);
- if (keystore != null) {
- return new Wallet(keystore);
- } else {
- return null;
- }
- }
-
- public static Wallet mustFindWalletById(String id) {
- IMTKeystore keystore = keystoreMap.get(id);
- if (keystore == null) throw new TokenException(Messages.WALLET_NOT_FOUND);
- return new Wallet(keystore);
- }
-
-
- static File generateWalletFile(String walletID) {
- return new File(getDefaultKeyDirectory(), walletID + ".json");
- }
-
-
- static File getDefaultKeyDirectory() {
- File directory = new File(storage.getKeystoreDir(), "wallets");
- if (!directory.exists()) {
- directory.mkdirs();
- }
- return directory;
- }
-
- static boolean cleanKeystoreDirectory() {
- return deleteDir(getDefaultKeyDirectory());
- }
-
- private static Wallet persistWallet(IMTKeystore keystore, boolean overwrite) {
- Wallet wallet = flushWallet(keystore, overwrite);
- Identity.getCurrentIdentity().addWallet(wallet);
- return wallet;
- }
-
- private static IMTKeystore findKeystoreByAddress(String type, String address) {
- if (Strings.isNullOrEmpty(address)) return null;
-
- for (IMTKeystore keystore : keystoreMap.values()) {
-
- if (Strings.isNullOrEmpty(keystore.getAddress())) {
- continue;
- }
-
- if (keystore.getMetadata().getChainType().equals(type) && keystore.getAddress().equals(address)) {
- return keystore;
- }
- }
-
- return null;
- }
-
- public static Wallet findWalletByAddress(String type, String address) {
- IMTKeystore keystore = findKeystoreByAddress(type, address);
- if (keystore != null) {
- return new Wallet(keystore);
- }
- return null;
- }
-
-
- private static Wallet flushWallet(IMTKeystore keystore, boolean overwrite) {
-
- IMTKeystore existsKeystore = findKeystoreByAddress(keystore.getMetadata().getChainType(), keystore.getAddress());
- if (existsKeystore != null) {
- if (!overwrite) {
- throw new TokenException(Messages.WALLET_EXISTS);
- } else {
- keystore.setId(existsKeystore.getId());
- }
- }
-
- File file = generateWalletFile(keystore.getId());
- writeToFile(keystore, file);
- keystoreMap.put(keystore.getId(), keystore);
- return new Wallet(keystore);
- }
-
- private static void writeToFile(Keystore keyStore, File destination) {
- try {
- WRITE_MAPPER.writeValue(destination, keyStore);
- } catch (IOException ex) {
- throw new TokenException(Messages.WALLET_STORE_FAIL, ex);
- }
- }
-
- private static boolean deleteDir(File dir) {
- if (dir.isDirectory()) {
- String[] children = dir.list();
- if (children != null) {
- for (String child : children) {
- if (!deleteDir(new File(dir, child))) {
- return false;
- }
- }
- }
- }
- return dir.delete();
- }
-
- private static V3Keystore validateKeystore(String keystoreContent, String password) {
- V3Keystore importedKeystore = unmarshalKeystore(keystoreContent, V3Keystore.class);
- if (Strings.isNullOrEmpty(importedKeystore.getAddress()) || importedKeystore.getCrypto() == null) {
- throw new TokenException(Messages.WALLET_INVALID_KEYSTORE);
- }
-
- importedKeystore.getCrypto().validate();
-
- if (!importedKeystore.verifyPassword(password))
- throw new TokenException(Messages.MAC_UNMATCH);
-
- byte[] prvKey = importedKeystore.decryptCiphertext(password);
- String address = new EthereumAddressCreator().fromPrivateKey(prvKey);
- if (Strings.isNullOrEmpty(address) || !address.equalsIgnoreCase(importedKeystore.getAddress())) {
- throw new TokenException(Messages.PRIVATE_KEY_ADDRESS_NOT_MATCH);
- }
- return importedKeystore;
- }
-
- private static IMTKeystore mustFindKeystoreById(String id) {
- IMTKeystore keystore = keystoreMap.get(id);
- if (keystore == null) {
- throw new TokenException(Messages.WALLET_NOT_FOUND);
- }
-
- return keystore;
- }
-
- private static T unmarshalKeystore(String keystoreContent, Class clazz) {
- try {
- return READ_MAPPER.readValue(keystoreContent, clazz);
- } catch (IOException ex) {
- throw new TokenException(Messages.WALLET_INVALID_KEYSTORE, ex);
- }
- }
-
- public static void scanWallets() {
- File directory = getDefaultKeyDirectory();
-
- keystoreMap.clear();
- File[] files = directory.listFiles();
- if (files == null) {
- return;
- }
- for (File file : files) {
- if (!file.getName().startsWith("identity")) {
- try {
- IMTKeystore keystore = null;
- CharSource charSource = Files.asCharSource(file, Charset.forName("UTF-8"));
- String jsonContent = charSource.read();
- JSONObject jsonObject = new JSONObject(jsonContent);
- int version = jsonObject.getInt("version");
- if (version == 3) {
- if (jsonContent.contains("encMnemonic")) {
- keystore = unmarshalKeystore(jsonContent, V3MnemonicKeystore.class);
- } else if (jsonObject.has("imTokenMeta") && ChainType.EOS.equals(jsonObject.getJSONObject("imTokenMeta").getString("chainType"))) {
- keystore = unmarshalKeystore(jsonContent, LegacyEOSKeystore.class);
- } else {
- keystore = unmarshalKeystore(jsonContent, V3Keystore.class);
- }
- } else if (version == 1) {
- keystore = unmarshalKeystore(jsonContent, V3Keystore.class);
- } else if (version == 44) {
- keystore = unmarshalKeystore(jsonContent, HDMnemonicKeystore.class);
- } else if (version == 10001) {
- keystore = unmarshalKeystore(jsonContent, EOSKeystore.class);
- }
-
- if (keystore != null) {
- keystoreMap.put(keystore.getId(), keystore);
- }
- } catch (Exception ignored) {
- }
- }
- }
- }
-
- private WalletManager() {
- }
-
-}
+package org.consenlabs.tokencore.wallet;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.databind.DeserializationFeature;
+import com.fasterxml.jackson.databind.MapperFeature;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.google.common.base.Strings;
+import com.google.common.io.CharSource;
+import com.google.common.io.Files;
+import org.bitcoinj.crypto.DeterministicKey;
+import org.bitcoinj.wallet.DeterministicKeyChain;
+import org.bitcoinj.wallet.DeterministicSeed;
+import org.consenlabs.tokencore.foundation.utils.MnemonicUtil;
+import org.consenlabs.tokencore.foundation.utils.NumericUtil;
+import org.consenlabs.tokencore.wallet.address.AddressCreatorManager;
+import org.consenlabs.tokencore.wallet.address.EthereumAddressCreator;
+import org.consenlabs.tokencore.wallet.keystore.*;
+import org.consenlabs.tokencore.wallet.model.*;
+import org.consenlabs.tokencore.wallet.validators.PrivateKeyValidator;
+import org.json.JSONObject;
+
+import javax.annotation.Nullable;
+import java.io.File;
+import java.io.IOException;
+import java.nio.charset.Charset;
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.ConcurrentHashMap;
+
+public class WalletManager {
+ private static final ConcurrentHashMap keystoreMap = new ConcurrentHashMap<>();
+
+ private static final ObjectMapper WRITE_MAPPER = new ObjectMapper();
+ private static final ObjectMapper READ_MAPPER = new ObjectMapper();
+
+ static {
+ WRITE_MAPPER.setSerializationInclusion(JsonInclude.Include.NON_NULL);
+ READ_MAPPER.configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES, true);
+ READ_MAPPER.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
+ READ_MAPPER.configure(DeserializationFeature.FAIL_ON_MISSING_CREATOR_PROPERTIES, true);
+ }
+
+ public static KeystoreStorage storage;
+
+ static ObjectMapper getWriteMapper() {
+ return WRITE_MAPPER;
+ }
+
+ static Wallet createWallet(IMTKeystore keystore) {
+ File file = generateWalletFile(keystore.getId());
+ writeToFile(keystore, file);
+ keystoreMap.put(keystore.getId(), keystore);
+ return new Wallet(keystore);
+ }
+
+ public static ConcurrentHashMap getKeyMap(){
+ return keystoreMap;
+ }
+
+ public static void changePassword(String id, String oldPassword, String newPassword) {
+ IMTKeystore keystore = mustFindKeystoreById(id);
+ IMTKeystore newKeystore = (IMTKeystore) keystore.changePassword(oldPassword, newPassword);
+ flushWallet(newKeystore, true);
+ }
+
+ public static String exportPrivateKey(String id, String password) {
+ Wallet wallet = mustFindWalletById(id);
+ return wallet.exportPrivateKey(password);
+ }
+
+ public static List exportPrivateKeys(String id, String password) {
+ Wallet wallet = mustFindWalletById(id);
+ return wallet.exportPrivateKeys(password);
+ }
+
+ public static MnemonicAndPath exportMnemonic(String id, String password) {
+ Wallet wallet = mustFindWalletById(id);
+ return wallet.exportMnemonic(password);
+ }
+
+ public static String exportKeystore(String id, String password) {
+ Wallet wallet = mustFindWalletById(id);
+ return wallet.exportKeystore(password);
+ }
+
+ public static void removeWallet(String id, String password) {
+ Wallet wallet = mustFindWalletById(id);
+ if (!wallet.verifyPassword(password)) {
+ throw new TokenException(Messages.WALLET_INVALID_PASSWORD);
+ }
+ if (wallet.delete(password)) {
+ Identity.getCurrentIdentity().removeWallet(id);
+ keystoreMap.remove(id);
+ }
+ }
+
+ public static void clearKeystoreMap() {
+ keystoreMap.clear();
+ }
+
+ public static Wallet importWalletFromKeystore(Metadata metadata, String keystoreContent, String password, boolean overwrite) {
+ WalletKeystore importedKeystore = validateKeystore(keystoreContent, password, metadata.getChainType());
+
+ if (metadata.getSource() == null)
+ metadata.setSource(Metadata.FROM_KEYSTORE);
+
+ String privateKey = NumericUtil.bytesToHex(importedKeystore.decryptCiphertext(password));
+ try {
+ new PrivateKeyValidator(privateKey).validate();
+ } catch (TokenException ex) {
+ if (Messages.PRIVATE_KEY_INVALID.equals(ex.getMessage())) {
+ throw new TokenException(Messages.KEYSTORE_CONTAINS_INVALID_PRIVATE_KEY);
+ } else {
+ throw ex;
+ }
+ }
+ return importWalletFromPrivateKey(metadata, privateKey, password, overwrite);
+ }
+
+ public static Wallet importWalletFromPrivateKey(Metadata metadata, String prvKeyHex, String password, boolean overwrite) {
+ IMTKeystore keystore = V3Keystore.create(metadata, password, prvKeyHex);
+ Wallet wallet = flushWallet(keystore, overwrite);
+ Identity.getCurrentIdentity().addWallet(wallet);
+ return wallet;
+ }
+
+ /**
+ * Just for import EOS wallet
+ */
+ public static Wallet importWalletFromPrivateKeys(Metadata metadata, String accountName, List prvKeys, List permissions, String password, boolean overwrite) {
+ IMTKeystore keystore = null;
+ if (!ChainType.EOS.equalsIgnoreCase(metadata.getChainType())) {
+ throw new TokenException("This method is only for importing EOS wallet");
+ }
+ keystore = EOSKeystore.create(metadata, password, accountName, prvKeys, permissions);
+ return persistWallet(keystore, overwrite);
+ }
+
+ /**
+ * use the importWalletFromPrivateKeys
+ */
+ @Deprecated
+ public static Wallet importWalletFromPrivateKey(Metadata metadata, String accountName, String prvKeyHex, String password, boolean overwrite) {
+ IMTKeystore keystore = LegacyEOSKeystore.create(metadata, accountName, password, prvKeyHex);
+ return persistWallet(keystore, overwrite);
+ }
+
+
+ /**
+ * import wallet from mnemonic
+ *
+ * @param metadata
+ * @param accountName only for EOS
+ * @param mnemonic
+ * @param path
+ * @param permissions only for EOS
+ * @param password
+ * @param overwrite
+ * @return
+ */
+ public static Wallet importWalletFromMnemonic(Metadata metadata, @Nullable String accountName, String mnemonic, String path, @Nullable List permissions, String password, boolean overwrite) {
+
+ if (metadata.getSource() == null)
+ metadata.setSource(Metadata.FROM_MNEMONIC);
+ IMTKeystore keystore = null;
+ List mnemonicCodes = MnemonicUtil.toMnemonicCodes(mnemonic);
+ MnemonicUtil.validateMnemonics(mnemonicCodes);
+ switch (metadata.getChainType()) {
+ case ChainType.ETHEREUM:
+ case ChainType.TRON:
+ case ChainType.FILECOIN:
+ keystore = V3MnemonicKeystore.create(metadata, password, mnemonicCodes, path);
+ break;
+ case ChainType.BITCOIN:
+ case ChainType.LITECOIN:
+ case ChainType.DASH:
+ case ChainType.DOGECOIN:
+ case ChainType.BITCOINCASH:
+ case ChainType.BITCOINSV:
+ keystore = HDMnemonicKeystore.create(metadata, password, mnemonicCodes, path);
+ break;
+ case ChainType.EOS:
+ keystore = EOSKeystore.create(metadata, password, accountName, mnemonicCodes, path, permissions);
+ break;
+ default:
+ throw new TokenException(String.format("Mnemonic import not supported for chain: %s", metadata.getChainType()));
+ }
+
+ return persistWallet(keystore, overwrite);
+ }
+
+ public static Wallet importWalletFromMnemonic(Metadata metadata, String mnemonic, String path, String password, boolean overwrite) {
+ return importWalletFromMnemonic(metadata, null, mnemonic, path, null, password, overwrite);
+ }
+
+ public static Wallet findWalletByPrivateKey(String chainType, String network, String privateKey, String segWit) {
+ if (ChainType.ETHEREUM.equals(chainType)) {
+ new PrivateKeyValidator(privateKey).validate();
+ }
+ Network net = new Network(network);
+ String address = AddressCreatorManager.getInstance(chainType, net.isMainnet(), segWit).fromPrivateKey(privateKey);
+ return findWalletByAddress(chainType, address);
+ }
+
+ public static Wallet findWalletByKeystore(String chainType, String keystoreContent, String password) {
+ WalletKeystore walletKeystore = validateKeystore(keystoreContent, password, chainType);
+
+ byte[] prvKeyBytes = walletKeystore.decryptCiphertext(password);
+ Metadata metadata = ((IMTKeystore) walletKeystore).getMetadata();
+ String address = deriveAddressByChain(chainType, metadata, prvKeyBytes);
+ return findWalletByAddress(chainType, address);
+ }
+
+ public static Wallet findWalletByMnemonic(String chainType, String network, String mnemonic, String path, String segWit) {
+ List mnemonicCodes = MnemonicUtil.toMnemonicCodes(mnemonic);
+ MnemonicUtil.validateMnemonics(mnemonicCodes);
+ DeterministicSeed seed = new DeterministicSeed(mnemonicCodes, null, "", 0L);
+ DeterministicKeyChain keyChain = DeterministicKeyChain.builder().seed(seed).build();
+ if (Strings.isNullOrEmpty(path)) {
+ throw new TokenException(Messages.INVALID_MNEMONIC_PATH);
+ }
+
+ if (isBitcoinFamily(chainType)) {
+ path += "/0/0";
+ }
+
+ DeterministicKey key = keyChain.getKeyByPath(BIP44Util.generatePath(path), true);
+ Network net = new Network(network);
+ String address = AddressCreatorManager.getInstance(chainType, net.isMainnet(), segWit).fromPrivateKey(key.getPrivateKeyAsHex());
+ return findWalletByAddress(chainType, address);
+ }
+
+ public static Wallet switchBTCWalletMode(String id, String password, String model) {
+ Wallet wallet = mustFindWalletById(id);
+ // !!! Warning !!! You must verify password before you write content to keystore
+ if (!wallet.getMetadata().getChainType().equalsIgnoreCase(ChainType.BITCOIN))
+ throw new TokenException("Only Bitcoin wallets can switch SegWit mode");
+ Metadata metadata = wallet.getMetadata().clone();
+ if (metadata.getSegWit().equalsIgnoreCase(model)) {
+ return wallet;
+ }
+
+ metadata.setSegWit(model);
+ IMTKeystore keystore;
+ if (wallet.hasMnemonic()) {
+
+ MnemonicAndPath mnemonicAndPath = wallet.exportMnemonic(password);
+ String path = BIP44Util.getBTCMnemonicPath(model, metadata.isMainNet());
+ List mnemonicCodes = MnemonicUtil.toMnemonicCodes(mnemonicAndPath.getMnemonic());
+ keystore = new HDMnemonicKeystore(metadata, password, mnemonicCodes, path, wallet.getId());
+ } else {
+ String prvKey = wallet.exportPrivateKey(password);
+ keystore = new V3Keystore(metadata, password, prvKey, wallet.getId());
+
+ }
+ flushWallet(keystore, false);
+ keystoreMap.put(wallet.getId(), keystore);
+ return new Wallet(keystore);
+ }
+
+ public static Wallet setAccountName(String id, String accountName) {
+ Wallet wallet = mustFindWalletById(id);
+ wallet.setAccountName(accountName);
+ return persistWallet(wallet.getKeystore(), true);
+ }
+
+ static Wallet findWalletById(String id) {
+ IMTKeystore keystore = keystoreMap.get(id);
+ if (keystore != null) {
+ return new Wallet(keystore);
+ } else {
+ return null;
+ }
+ }
+
+ public static Wallet mustFindWalletById(String id) {
+ IMTKeystore keystore = keystoreMap.get(id);
+ if (keystore == null) throw new TokenException(Messages.WALLET_NOT_FOUND);
+ return new Wallet(keystore);
+ }
+
+
+ static File generateWalletFile(String walletID) {
+ return new File(getDefaultKeyDirectory(), walletID + ".json");
+ }
+
+
+ static File getDefaultKeyDirectory() {
+ File directory = new File(storage.getKeystoreDir(), "wallets");
+ if (!directory.exists()) {
+ directory.mkdirs();
+ }
+ return directory;
+ }
+
+ static boolean cleanKeystoreDirectory() {
+ return deleteDir(getDefaultKeyDirectory());
+ }
+
+ private static Wallet persistWallet(IMTKeystore keystore, boolean overwrite) {
+ Wallet wallet = flushWallet(keystore, overwrite);
+ Identity.getCurrentIdentity().addWallet(wallet);
+ return wallet;
+ }
+
+ private static IMTKeystore findKeystoreByAddress(String type, String address) {
+ if (Strings.isNullOrEmpty(address)) return null;
+
+ for (IMTKeystore keystore : keystoreMap.values()) {
+
+ if (Strings.isNullOrEmpty(keystore.getAddress())) {
+ continue;
+ }
+
+ if (keystore.getMetadata().getChainType().equals(type) && keystore.getAddress().equals(address)) {
+ return keystore;
+ }
+ }
+
+ return null;
+ }
+
+ public static Wallet findWalletByAddress(String type, String address) {
+ IMTKeystore keystore = findKeystoreByAddress(type, address);
+ if (keystore != null) {
+ return new Wallet(keystore);
+ }
+ return null;
+ }
+
+
+ private static Wallet flushWallet(IMTKeystore keystore, boolean overwrite) {
+
+ IMTKeystore existsKeystore = findKeystoreByAddress(keystore.getMetadata().getChainType(), keystore.getAddress());
+ if (existsKeystore != null) {
+ if (!overwrite) {
+ throw new TokenException(Messages.WALLET_EXISTS);
+ } else {
+ keystore.setId(existsKeystore.getId());
+ }
+ }
+
+ File file = generateWalletFile(keystore.getId());
+ writeToFile(keystore, file);
+ keystoreMap.put(keystore.getId(), keystore);
+ return new Wallet(keystore);
+ }
+
+ private static void writeToFile(Keystore keyStore, File destination) {
+ try {
+ WRITE_MAPPER.writeValue(destination, keyStore);
+ } catch (IOException ex) {
+ throw new TokenException(Messages.WALLET_STORE_FAIL, ex);
+ }
+ }
+
+ private static boolean deleteDir(File dir) {
+ if (dir.isDirectory()) {
+ String[] children = dir.list();
+ if (children != null) {
+ for (String child : children) {
+ if (!deleteDir(new File(dir, child))) {
+ return false;
+ }
+ }
+ }
+ }
+ return dir.delete();
+ }
+
+ private static V3Keystore validateKeystore(String keystoreContent, String password, String chainType) {
+ V3Keystore importedKeystore = unmarshalKeystore(keystoreContent, V3Keystore.class);
+ if (Strings.isNullOrEmpty(importedKeystore.getAddress()) || importedKeystore.getCrypto() == null) {
+ throw new TokenException(Messages.WALLET_INVALID_KEYSTORE);
+ }
+
+ importedKeystore.getCrypto().validate();
+
+ if (!importedKeystore.verifyPassword(password))
+ throw new TokenException(Messages.MAC_UNMATCH);
+
+ byte[] prvKey = importedKeystore.decryptCiphertext(password);
+ Metadata metadata = importedKeystore.getMetadata();
+ String derivedAddress = deriveAddressByChain(chainType, metadata, prvKey);
+ if (isEvmFamily(chainType)) {
+ if (Strings.isNullOrEmpty(derivedAddress) || !derivedAddress.equalsIgnoreCase(importedKeystore.getAddress())) {
+ throw new TokenException(Messages.PRIVATE_KEY_ADDRESS_NOT_MATCH);
+ }
+ }
+ return importedKeystore;
+ }
+
+ private static String deriveAddressByChain(String chainType, Metadata metadata, byte[] prvKey) {
+ boolean isMainnet = metadata != null && metadata.isMainNet();
+ String segWit = metadata == null ? Metadata.NONE : metadata.getSegWit();
+
+ if (isEvmFamily(chainType)) {
+ return new EthereumAddressCreator().fromPrivateKey(prvKey);
+ }
+
+ return AddressCreatorManager.getInstance(chainType, isMainnet, segWit).fromPrivateKey(prvKey);
+ }
+
+
+ private static boolean isBitcoinFamily(String chainType) {
+ return ChainType.BITCOIN.equalsIgnoreCase(chainType)
+ || ChainType.LITECOIN.equalsIgnoreCase(chainType)
+ || ChainType.DOGECOIN.equalsIgnoreCase(chainType)
+ || ChainType.DASH.equalsIgnoreCase(chainType)
+ || ChainType.BITCOINCASH.equalsIgnoreCase(chainType)
+ || ChainType.BITCOINSV.equalsIgnoreCase(chainType);
+ }
+ private static boolean isEvmFamily(String chainType) {
+ return ChainType.ETHEREUM.equalsIgnoreCase(chainType) || chainType.toUpperCase().contains("EVM");
+ }
+
+ private static IMTKeystore mustFindKeystoreById(String id) {
+ IMTKeystore keystore = keystoreMap.get(id);
+ if (keystore == null) {
+ throw new TokenException(Messages.WALLET_NOT_FOUND);
+ }
+
+ return keystore;
+ }
+
+ private static T unmarshalKeystore(String keystoreContent, Class clazz) {
+ try {
+ return READ_MAPPER.readValue(keystoreContent, clazz);
+ } catch (IOException ex) {
+ throw new TokenException(Messages.WALLET_INVALID_KEYSTORE, ex);
+ }
+ }
+
+ public static void scanWallets() {
+ File directory = getDefaultKeyDirectory();
+
+ keystoreMap.clear();
+ File[] files = directory.listFiles();
+ if (files == null) {
+ return;
+ }
+ for (File file : files) {
+ if (!file.getName().startsWith("identity")) {
+ try {
+ IMTKeystore keystore = null;
+ CharSource charSource = Files.asCharSource(file, Charset.forName("UTF-8"));
+ String jsonContent = charSource.read();
+ JSONObject jsonObject = new JSONObject(jsonContent);
+ int version = jsonObject.getInt("version");
+ if (version == 3) {
+ if (jsonContent.contains("encMnemonic")) {
+ keystore = unmarshalKeystore(jsonContent, V3MnemonicKeystore.class);
+ } else if (jsonObject.has("imTokenMeta") && ChainType.EOS.equals(jsonObject.getJSONObject("imTokenMeta").getString("chainType"))) {
+ keystore = unmarshalKeystore(jsonContent, LegacyEOSKeystore.class);
+ } else {
+ keystore = unmarshalKeystore(jsonContent, V3Keystore.class);
+ }
+ } else if (version == 1) {
+ keystore = unmarshalKeystore(jsonContent, V3Keystore.class);
+ } else if (version == 44) {
+ keystore = unmarshalKeystore(jsonContent, HDMnemonicKeystore.class);
+ } else if (version == 10001) {
+ keystore = unmarshalKeystore(jsonContent, EOSKeystore.class);
+ }
+
+ if (keystore != null) {
+ keystoreMap.put(keystore.getId(), keystore);
+ }
+ } catch (Exception ignored) {
+ }
+ }
+ }
+ }
+
+ private WalletManager() {
+ }
+
+}
diff --git a/src/main/java/org/consenlabs/tokencore/wallet/keystore/HDMnemonicKeystore.java b/src/main/java/org/consenlabs/tokencore/wallet/keystore/HDMnemonicKeystore.java
index cc95b44..6dda4af 100755
--- a/src/main/java/org/consenlabs/tokencore/wallet/keystore/HDMnemonicKeystore.java
+++ b/src/main/java/org/consenlabs/tokencore/wallet/keystore/HDMnemonicKeystore.java
@@ -1,191 +1,191 @@
-package org.consenlabs.tokencore.wallet.keystore;
-
-import com.fasterxml.jackson.annotation.JsonIgnore;
-import com.google.common.base.Joiner;
-import com.google.common.base.Strings;
-import com.google.common.io.BaseEncoding;
-import com.subgraph.orchid.encoders.Hex;
-import org.bitcoinj.core.NetworkParameters;
-import org.bitcoinj.crypto.ChildNumber;
-import org.bitcoinj.crypto.DeterministicKey;
-import org.bitcoinj.crypto.HDKeyDerivation;
-import org.bitcoinj.wallet.DeterministicKeyChain;
-import org.bitcoinj.wallet.DeterministicSeed;
-import org.consenlabs.tokencore.foundation.crypto.AES;
-import org.consenlabs.tokencore.foundation.crypto.Crypto;
-import org.consenlabs.tokencore.foundation.crypto.EncPair;
-import org.consenlabs.tokencore.foundation.utils.DateUtil;
-import org.consenlabs.tokencore.foundation.utils.MetaUtil;
-import org.consenlabs.tokencore.foundation.utils.MnemonicUtil;
-import org.consenlabs.tokencore.wallet.address.SegWitBitcoinAddressCreator;
-import org.consenlabs.tokencore.wallet.model.BIP44Util;
-import org.consenlabs.tokencore.wallet.model.Messages;
-import org.consenlabs.tokencore.wallet.model.Metadata;
-import org.consenlabs.tokencore.wallet.model.TokenException;
-
-import java.nio.charset.Charset;
-import java.util.Arrays;
-import java.util.List;
-import java.util.UUID;
-
-/**
- * Created by xyz on 2018/2/5.
- */
-
-public final class HDMnemonicKeystore extends IMTKeystore implements EncMnemonicKeystore {
-
- // !!! Don't use this key in production !!!
- public static String XPubCommonKey128 = "B888D25EC8C12BD5043777B1AC49F872";
- public static String XPubCommonIv = "9C0C30889CBCC5E01AB5B2BB88715799";
-
- static int VERSION = 44;
- private EncPair encMnemonic;
- private String mnemonicPath;
- private String xpub;
-
- public Info getInfo() {
- return info;
- }
-
- public void setInfo(Info info) {
- this.info = info;
- }
-
- private Info info;
-
- @Override
- public EncPair getEncMnemonic() {
- return encMnemonic;
- }
-
- @Override
- public void setEncMnemonic(EncPair encMnemonic) {
- this.encMnemonic = encMnemonic;
- }
-
- @Override
- public String getMnemonicPath() {
- return mnemonicPath;
- }
-
- public void setMnemonicPath(String mnemonicPath) {
- this.mnemonicPath = mnemonicPath;
- }
-
- public String getXpub() {
- return this.xpub;
- }
-
- public void setXpub(String xpub) {
- this.xpub = xpub;
- }
-
- public HDMnemonicKeystore() {
- super();
- }
-
- public static HDMnemonicKeystore create(Metadata metadata, String password, List mnemonics, String path) {
- return new HDMnemonicKeystore(metadata, password, mnemonics, path, "");
- }
-
- public HDMnemonicKeystore(Metadata metadata, String password, List mnemonics, String path, String id) {
- MnemonicUtil.validateMnemonics(mnemonics);
- DeterministicSeed seed = new DeterministicSeed(mnemonics, null, "", 0L);
- DeterministicKeyChain keyChain = DeterministicKeyChain.builder().seed(seed).build();
- this.mnemonicPath = path;
-
- DeterministicKey parent = keyChain.getKeyByPath(BIP44Util.generatePath(path), true);
- NetworkParameters networkParameters = MetaUtil.getNetWork(metadata);
- this.xpub = parent.serializePubB58(networkParameters);
- String xprv = parent.serializePrivB58(networkParameters);
- DeterministicKey mainAddressKey = keyChain.getKeyByPath(BIP44Util.generatePath(path + "/0/0"), true);
- if (Metadata.P2WPKH.equals(metadata.getSegWit())) {
- this.address = new SegWitBitcoinAddressCreator(networkParameters).fromPrivateKey(mainAddressKey.getPrivateKeyAsHex());
- } else {
- this.address = mainAddressKey.toAddress(networkParameters).toBase58();
- }
- if (metadata.getTimestamp() == 0) {
- metadata.setTimestamp(DateUtil.getUTCTime());
- }
- metadata.setWalletType(Metadata.HD);
-
- this.crypto = Crypto.createPBKDF2CryptoWithKDFCached(password, xprv.getBytes(Charset.forName("UTF-8")));
- this.metadata = metadata;
- this.encMnemonic = crypto.deriveEncPair(password, Joiner.on(" ").join(mnemonics).getBytes());
- this.crypto.clearCachedDerivedKey();
-
- this.version = VERSION;
- this.info = new Info();
- this.id = Strings.isNullOrEmpty(id) ? UUID.randomUUID().toString() : id;
- }
-
-
- @Override
- public Keystore changePassword(String oldPassword, String newPassword) {
- String mnemonic = new String(getCrypto().decryptEncPair(oldPassword, encMnemonic));
- List mnemonicCodes = Arrays.asList(mnemonic.split(" "));
- return new HDMnemonicKeystore(metadata, newPassword, mnemonicCodes, this.mnemonicPath, this.id);
- }
-
- @JsonIgnore
- public String getEncryptXPub() {
- String plainText = this.xpub;
- try {
-
- byte[] commonKey128 = Hex.decode(XPubCommonKey128);
- byte[] clean = plainText.getBytes();
- byte[] commonIv = Hex.decode(XPubCommonIv);
- byte[] encrypted = AES.encryptByCBC(clean, commonKey128, commonIv);
- return BaseEncoding.base64().encode(encrypted);
- } catch (Exception ex) {
- throw new TokenException(Messages.ENCRYPT_XPUB_ERROR);
- }
- }
-
- public String newReceiveAddress(int nextIdx) {
- NetworkParameters networkParameters = MetaUtil.getNetWork(this.metadata);
- DeterministicKey key = DeterministicKey.deserializeB58(this.xpub, networkParameters);
- DeterministicKey changeKey = HDKeyDerivation.deriveChildKey(key, ChildNumber.ZERO);
- DeterministicKey indexKey = HDKeyDerivation.deriveChildKey(changeKey, new ChildNumber(nextIdx));
- if (Metadata.P2WPKH.equals(metadata.getSegWit())) {
- return new SegWitBitcoinAddressCreator(networkParameters).fromPrivateKey(indexKey).toBase58();
- } else {
- return indexKey.toAddress(networkParameters).toBase58();
- }
- }
-
-
- public static class Info {
- private String curve = "secp256k1";
- private String purpose = "sign";
-
- public Info() {
- }
-
- public String getCurve() {
- return curve;
- }
-
- public void setCurve(String curve) {
- this.curve = curve;
- }
-
- public String getPurpose() {
- return purpose;
- }
-
- public void setPurpose(String purpose) {
- this.purpose = purpose;
- }
-
- @Deprecated
- public String getPurpuse() {
- return purpose;
- }
-
- @Deprecated
- public void setPurpuse(String purpuse) {
- this.purpose = purpuse;
- }
- }
-}
+package org.consenlabs.tokencore.wallet.keystore;
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.google.common.base.Joiner;
+import com.google.common.base.Strings;
+import com.google.common.io.BaseEncoding;
+import com.subgraph.orchid.encoders.Hex;
+import org.bitcoinj.core.NetworkParameters;
+import org.bitcoinj.crypto.ChildNumber;
+import org.bitcoinj.crypto.DeterministicKey;
+import org.bitcoinj.crypto.HDKeyDerivation;
+import org.bitcoinj.wallet.DeterministicKeyChain;
+import org.bitcoinj.wallet.DeterministicSeed;
+import org.consenlabs.tokencore.foundation.crypto.AES;
+import org.consenlabs.tokencore.foundation.crypto.Crypto;
+import org.consenlabs.tokencore.foundation.crypto.EncPair;
+import org.consenlabs.tokencore.foundation.utils.DateUtil;
+import org.consenlabs.tokencore.foundation.utils.MetaUtil;
+import org.consenlabs.tokencore.foundation.utils.MnemonicUtil;
+import org.consenlabs.tokencore.wallet.address.SegWitBitcoinAddressCreator;
+import org.consenlabs.tokencore.wallet.model.BIP44Util;
+import org.consenlabs.tokencore.wallet.model.Messages;
+import org.consenlabs.tokencore.wallet.model.Metadata;
+import org.consenlabs.tokencore.wallet.model.TokenException;
+
+import java.nio.charset.Charset;
+import java.util.Arrays;
+import java.util.List;
+import java.util.UUID;
+
+/**
+ * Created by xyz on 2018/2/5.
+ */
+
+public final class HDMnemonicKeystore extends IMTKeystore implements EncMnemonicKeystore {
+
+ // !!! Don't use this key in production !!!
+ public static String XPubCommonKey128 = "B888D25EC8C12BD5043777B1AC49F872";
+ public static String XPubCommonIv = "9C0C30889CBCC5E01AB5B2BB88715799";
+
+ static int VERSION = 44;
+ private EncPair encMnemonic;
+ private String mnemonicPath;
+ private String xpub;
+
+ public Info getInfo() {
+ return info;
+ }
+
+ public void setInfo(Info info) {
+ this.info = info;
+ }
+
+ private Info info;
+
+ @Override
+ public EncPair getEncMnemonic() {
+ return encMnemonic;
+ }
+
+ @Override
+ public void setEncMnemonic(EncPair encMnemonic) {
+ this.encMnemonic = encMnemonic;
+ }
+
+ @Override
+ public String getMnemonicPath() {
+ return mnemonicPath;
+ }
+
+ public void setMnemonicPath(String mnemonicPath) {
+ this.mnemonicPath = mnemonicPath;
+ }
+
+ public String getXpub() {
+ return this.xpub;
+ }
+
+ public void setXpub(String xpub) {
+ this.xpub = xpub;
+ }
+
+ public HDMnemonicKeystore() {
+ super();
+ }
+
+ public static HDMnemonicKeystore create(Metadata metadata, String password, List mnemonics, String path) {
+ return new HDMnemonicKeystore(metadata, password, mnemonics, path, "");
+ }
+
+ public HDMnemonicKeystore(Metadata metadata, String password, List mnemonics, String path, String id) {
+ MnemonicUtil.validateMnemonics(mnemonics);
+ DeterministicSeed seed = new DeterministicSeed(mnemonics, null, "", 0L);
+ DeterministicKeyChain keyChain = DeterministicKeyChain.builder().seed(seed).build();
+ this.mnemonicPath = path;
+
+ DeterministicKey parent = keyChain.getKeyByPath(BIP44Util.generatePath(path), true);
+ NetworkParameters networkParameters = MetaUtil.getNetWork(metadata);
+ this.xpub = parent.serializePubB58(networkParameters);
+ String xprv = parent.serializePrivB58(networkParameters);
+ DeterministicKey mainAddressKey = keyChain.getKeyByPath(BIP44Util.generatePath(path + "/0/0"), true);
+ if (Metadata.P2WPKH.equals(metadata.getSegWit())) {
+ this.address = new SegWitBitcoinAddressCreator(networkParameters).fromPrivateKey(mainAddressKey.getPrivateKeyAsHex());
+ } else {
+ this.address = mainAddressKey.toAddress(networkParameters).toBase58();
+ }
+ if (metadata.getTimestamp() == 0) {
+ metadata.setTimestamp(DateUtil.getUTCTime());
+ }
+ metadata.setWalletType(Metadata.HD);
+
+ this.crypto = Crypto.createPBKDF2CryptoWithKDFCached(password, xprv.getBytes(Charset.forName("UTF-8")));
+ this.metadata = metadata;
+ this.encMnemonic = crypto.deriveEncPair(password, Joiner.on(" ").join(mnemonics).getBytes());
+ this.crypto.clearCachedDerivedKey();
+
+ this.version = VERSION;
+ this.info = new Info();
+ this.id = Strings.isNullOrEmpty(id) ? UUID.randomUUID().toString() : id;
+ }
+
+
+ @Override
+ public Keystore changePassword(String oldPassword, String newPassword) {
+ String mnemonic = new String(getCrypto().decryptEncPair(oldPassword, encMnemonic));
+ List mnemonicCodes = MnemonicUtil.toMnemonicCodes(mnemonic);
+ return new HDMnemonicKeystore(metadata, newPassword, mnemonicCodes, this.mnemonicPath, this.id);
+ }
+
+ @JsonIgnore
+ public String getEncryptXPub() {
+ String plainText = this.xpub;
+ try {
+
+ byte[] commonKey128 = Hex.decode(XPubCommonKey128);
+ byte[] clean = plainText.getBytes();
+ byte[] commonIv = Hex.decode(XPubCommonIv);
+ byte[] encrypted = AES.encryptByCBC(clean, commonKey128, commonIv);
+ return BaseEncoding.base64().encode(encrypted);
+ } catch (Exception ex) {
+ throw new TokenException(Messages.ENCRYPT_XPUB_ERROR);
+ }
+ }
+
+ public String newReceiveAddress(int nextIdx) {
+ NetworkParameters networkParameters = MetaUtil.getNetWork(this.metadata);
+ DeterministicKey key = DeterministicKey.deserializeB58(this.xpub, networkParameters);
+ DeterministicKey changeKey = HDKeyDerivation.deriveChildKey(key, ChildNumber.ZERO);
+ DeterministicKey indexKey = HDKeyDerivation.deriveChildKey(changeKey, new ChildNumber(nextIdx));
+ if (Metadata.P2WPKH.equals(metadata.getSegWit())) {
+ return new SegWitBitcoinAddressCreator(networkParameters).fromPrivateKey(indexKey).toBase58();
+ } else {
+ return indexKey.toAddress(networkParameters).toBase58();
+ }
+ }
+
+
+ public static class Info {
+ private String curve = "secp256k1";
+ private String purpose = "sign";
+
+ public Info() {
+ }
+
+ public String getCurve() {
+ return curve;
+ }
+
+ public void setCurve(String curve) {
+ this.curve = curve;
+ }
+
+ public String getPurpose() {
+ return purpose;
+ }
+
+ public void setPurpose(String purpose) {
+ this.purpose = purpose;
+ }
+
+ @Deprecated
+ public String getPurpuse() {
+ return purpose;
+ }
+
+ @Deprecated
+ public void setPurpuse(String purpuse) {
+ this.purpose = purpuse;
+ }
+ }
+}
diff --git a/src/main/java/org/consenlabs/tokencore/wallet/keystore/V3MnemonicKeystore.java b/src/main/java/org/consenlabs/tokencore/wallet/keystore/V3MnemonicKeystore.java
index 0c2db31..a3aaa41 100755
--- a/src/main/java/org/consenlabs/tokencore/wallet/keystore/V3MnemonicKeystore.java
+++ b/src/main/java/org/consenlabs/tokencore/wallet/keystore/V3MnemonicKeystore.java
@@ -1,86 +1,86 @@
-package org.consenlabs.tokencore.wallet.keystore;
-
-import com.google.common.base.Joiner;
-import com.google.common.base.Strings;
-import org.bitcoinj.crypto.ChildNumber;
-import org.bitcoinj.wallet.DeterministicKeyChain;
-import org.bitcoinj.wallet.DeterministicSeed;
-import org.consenlabs.tokencore.foundation.crypto.Crypto;
-import org.consenlabs.tokencore.foundation.crypto.EncPair;
-import org.consenlabs.tokencore.foundation.utils.DateUtil;
-import org.consenlabs.tokencore.foundation.utils.MnemonicUtil;
-import org.consenlabs.tokencore.wallet.address.AddressCreatorManager;
-import org.consenlabs.tokencore.wallet.model.BIP44Util;
-import org.consenlabs.tokencore.wallet.model.Metadata;
-
-import java.util.Arrays;
-import java.util.List;
-import java.util.UUID;
-
-/**
- * Created by xyz on 2018/2/5.
- */
-
-public class V3MnemonicKeystore extends IMTKeystore implements EncMnemonicKeystore, ExportableKeystore {
- private static final int VERSION = 3;
- private EncPair encMnemonic;
- private String mnemonicPath;
-
- @Override
- public EncPair getEncMnemonic() {
- return encMnemonic;
- }
-
- @Override
- public void setEncMnemonic(EncPair encMnemonic) {
- this.encMnemonic = encMnemonic;
- }
-
- @Override
- public String getMnemonicPath() {
- return this.mnemonicPath;
- }
-
- public void setMnemonicPath(String mnemonicPath) {
- this.mnemonicPath = mnemonicPath;
- }
-
- public V3MnemonicKeystore() {
- super();
- }
-
- public static V3MnemonicKeystore create(Metadata metadata, String password, List mnemonicCodes, String path) {
- return new V3MnemonicKeystore(metadata, password, mnemonicCodes, path, "");
- }
-
-
- private V3MnemonicKeystore(Metadata metadata, String password, List mnemonicCodes, String path, String id) {
- MnemonicUtil.validateMnemonics(mnemonicCodes);
- DeterministicSeed seed = new DeterministicSeed(mnemonicCodes, null, "", 0L);
- DeterministicKeyChain keyChain = DeterministicKeyChain.builder().seed(seed).build();
-
- this.mnemonicPath = path;
- List zeroPath = BIP44Util.generatePath(path);
-
- byte[] prvKeyBytes = keyChain.getKeyByPath(zeroPath, true).getPrivKeyBytes();
- this.crypto = Crypto.createPBKDF2CryptoWithKDFCached(password, prvKeyBytes);
- this.encMnemonic = crypto.deriveEncPair(password, Joiner.on(" ").join(mnemonicCodes).getBytes());
- this.crypto.clearCachedDerivedKey();
-
- this.address = AddressCreatorManager.getInstance(metadata.getChainType(), metadata.isMainNet(), metadata.getSegWit()).fromPrivateKey(prvKeyBytes);
- metadata.setTimestamp(DateUtil.getUTCTime());
- metadata.setWalletType(Metadata.V3);
- this.metadata = metadata;
- this.version = VERSION;
- this.id = Strings.isNullOrEmpty(id) ? UUID.randomUUID().toString() : id;
- }
-
-
-
- @Override
- public Keystore changePassword(String oldPassword, String newPassword) {
- String mnemonic = new String(getCrypto().decryptEncPair(oldPassword, this.getEncMnemonic()));
- List mnemonicCodes = Arrays.asList(mnemonic.split(" "));
- return new V3MnemonicKeystore(this.metadata, newPassword, mnemonicCodes, this.mnemonicPath, this.id);
- }
-}
+package org.consenlabs.tokencore.wallet.keystore;
+
+import com.google.common.base.Joiner;
+import com.google.common.base.Strings;
+import org.bitcoinj.crypto.ChildNumber;
+import org.bitcoinj.wallet.DeterministicKeyChain;
+import org.bitcoinj.wallet.DeterministicSeed;
+import org.consenlabs.tokencore.foundation.crypto.Crypto;
+import org.consenlabs.tokencore.foundation.crypto.EncPair;
+import org.consenlabs.tokencore.foundation.utils.DateUtil;
+import org.consenlabs.tokencore.foundation.utils.MnemonicUtil;
+import org.consenlabs.tokencore.wallet.address.AddressCreatorManager;
+import org.consenlabs.tokencore.wallet.model.BIP44Util;
+import org.consenlabs.tokencore.wallet.model.Metadata;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.UUID;
+
+/**
+ * Created by xyz on 2018/2/5.
+ */
+
+public class V3MnemonicKeystore extends IMTKeystore implements EncMnemonicKeystore, ExportableKeystore {
+ private static final int VERSION = 3;
+ private EncPair encMnemonic;
+ private String mnemonicPath;
+
+ @Override
+ public EncPair getEncMnemonic() {
+ return encMnemonic;
+ }
+
+ @Override
+ public void setEncMnemonic(EncPair encMnemonic) {
+ this.encMnemonic = encMnemonic;
+ }
+
+ @Override
+ public String getMnemonicPath() {
+ return this.mnemonicPath;
+ }
+
+ public void setMnemonicPath(String mnemonicPath) {
+ this.mnemonicPath = mnemonicPath;
+ }
+
+ public V3MnemonicKeystore() {
+ super();
+ }
+
+ public static V3MnemonicKeystore create(Metadata metadata, String password, List mnemonicCodes, String path) {
+ return new V3MnemonicKeystore(metadata, password, mnemonicCodes, path, "");
+ }
+
+
+ private V3MnemonicKeystore(Metadata metadata, String password, List mnemonicCodes, String path, String id) {
+ MnemonicUtil.validateMnemonics(mnemonicCodes);
+ DeterministicSeed seed = new DeterministicSeed(mnemonicCodes, null, "", 0L);
+ DeterministicKeyChain keyChain = DeterministicKeyChain.builder().seed(seed).build();
+
+ this.mnemonicPath = path;
+ List zeroPath = BIP44Util.generatePath(path);
+
+ byte[] prvKeyBytes = keyChain.getKeyByPath(zeroPath, true).getPrivKeyBytes();
+ this.crypto = Crypto.createPBKDF2CryptoWithKDFCached(password, prvKeyBytes);
+ this.encMnemonic = crypto.deriveEncPair(password, Joiner.on(" ").join(mnemonicCodes).getBytes());
+ this.crypto.clearCachedDerivedKey();
+
+ this.address = AddressCreatorManager.getInstance(metadata.getChainType(), metadata.isMainNet(), metadata.getSegWit()).fromPrivateKey(prvKeyBytes);
+ metadata.setTimestamp(DateUtil.getUTCTime());
+ metadata.setWalletType(Metadata.V3);
+ this.metadata = metadata;
+ this.version = VERSION;
+ this.id = Strings.isNullOrEmpty(id) ? UUID.randomUUID().toString() : id;
+ }
+
+
+
+ @Override
+ public Keystore changePassword(String oldPassword, String newPassword) {
+ String mnemonic = new String(getCrypto().decryptEncPair(oldPassword, this.getEncMnemonic()));
+ List mnemonicCodes = MnemonicUtil.toMnemonicCodes(mnemonic);
+ return new V3MnemonicKeystore(this.metadata, newPassword, mnemonicCodes, this.mnemonicPath, this.id);
+ }
+}
diff --git a/src/test/java/org/consenlabs/tokencore/foundation/utils/MnemonicUtilTest.java b/src/test/java/org/consenlabs/tokencore/foundation/utils/MnemonicUtilTest.java
index 32c234c..95397ee 100644
--- a/src/test/java/org/consenlabs/tokencore/foundation/utils/MnemonicUtilTest.java
+++ b/src/test/java/org/consenlabs/tokencore/foundation/utils/MnemonicUtilTest.java
@@ -57,6 +57,20 @@ void validateMnemonics_shouldRejectInvalidWords() {
assertThrows(TokenException.class, () -> MnemonicUtil.validateMnemonics(invalid));
}
+ @Test
+ void toMnemonicCodes_string_shouldTrimAndNormalizeWhitespace() {
+ String raw = " abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about ";
+ List codes = MnemonicUtil.toMnemonicCodes(raw);
+ assertEquals(12, codes.size());
+ assertEquals("abandon", codes.get(0));
+ assertEquals("about", codes.get(11));
+ }
+
+ @Test
+ void toMnemonicCodes_string_shouldRejectBlankInput() {
+ assertThrows(TokenException.class, () -> MnemonicUtil.toMnemonicCodes(" "));
+ }
+
@Test
void toMnemonicCodes_shouldConvertEntropy() {
byte[] entropy = new byte[16];
diff --git a/src/test/java/org/consenlabs/tokencore/wallet/WalletManagerTest.java b/src/test/java/org/consenlabs/tokencore/wallet/WalletManagerTest.java
index b044bdb..215a0ab 100644
--- a/src/test/java/org/consenlabs/tokencore/wallet/WalletManagerTest.java
+++ b/src/test/java/org/consenlabs/tokencore/wallet/WalletManagerTest.java
@@ -7,6 +7,7 @@
import java.io.File;
import java.nio.file.Path;
+import java.util.List;
import static org.junit.jupiter.api.Assertions.*;
@@ -77,6 +78,45 @@ void findWalletByAddress_shouldReturnWallet() {
assertEquals(ethWallet.getAddress(), found.getAddress());
}
+
+ @Test
+ void findWalletByKeystore_ethereum_shouldReturnWallet() {
+ Identity identity = Identity.createIdentity(
+ "test", "password123", "", Network.MAINNET, Metadata.P2WPKH);
+
+ Wallet ethWallet = identity.deriveWalletByMnemonics(
+ ChainType.ETHEREUM, "password123", MnemonicUtil.randomMnemonicCodes());
+
+ String keystore = WalletManager.exportKeystore(ethWallet.getId(), "password123");
+ Wallet found = WalletManager.findWalletByKeystore(ChainType.ETHEREUM, keystore, "password123");
+
+ assertNotNull(found);
+ assertEquals(ethWallet.getAddress(), found.getAddress());
+ }
+
+
+
+ @Test
+ void findWalletByMnemonic_dogecoin_shouldReturnWallet() {
+ Identity identity = Identity.createIdentity(
+ "test", "password123", "", Network.MAINNET, Metadata.NONE);
+
+ List mnemonics = MnemonicUtil.randomMnemonicCodes();
+ Wallet dogeWallet = identity.deriveWalletByMnemonics(
+ ChainType.DOGECOIN, "password123", mnemonics);
+
+ String mnemonic = String.join(" ", mnemonics);
+ Wallet found = WalletManager.findWalletByMnemonic(
+ ChainType.DOGECOIN,
+ Network.MAINNET,
+ mnemonic,
+ BIP44Util.DOGECOIN_MAINNET_PATH,
+ Metadata.NONE);
+
+ assertNotNull(found);
+ assertEquals(dogeWallet.getAddress(), found.getAddress());
+ }
+
@Test
void findWalletByAddress_notFound_shouldReturnNull() {
Identity.createIdentity(