diff --git a/README.md b/README.md
index 8051513..730613c 100644
--- a/README.md
+++ b/README.md
@@ -1,134 +1,184 @@
-
Tokencore
-
-
- Multi-chain cryptocurrency wallet core library for Java
-
-
-
-
-
-
-
-
-
-
-
-
- Quick Start (30s) •
- Integration •
- Recommended Minimum •
- Supported Chains
-
+# Tokencore
+
+Tokencore is a Java multi-chain wallet core library for exchange backends, custody systems, and wallet services.
+
+## What Tokencore provides
+
+- Multi-chain address generation
+- HD wallet derivation and mnemonic workflows
+- Encrypted keystore management
+- Offline transaction signing for major chain families
+
+Supported chains include:
+- **EVM**: Ethereum
+- **Bitcoin family**: Bitcoin, Litecoin, Dogecoin, Dash, Bitcoin Cash, Bitcoin SV
+- **Others**: TRON, Filecoin, EOS
---
-## Introduction
+## Core Features (Recommended Minimum)
-Tokencore is a lightweight Java library for wallet fundamentals: HD derivation, encrypted keystore management, and offline signing.
+- Java 8+
+- Gradle wrapper included (`./gradlew`)
-If your goal is "install and use immediately", start with the 30-second quick start below and only enable additional chains/features later.
+---
-## Quick Start (30 seconds)
+## Install
-### 1) Add dependency
+### Gradle
```gradle
repositories {
maven { url 'https://jitpack.io' }
}
+
dependencies {
implementation 'com.github.galaxyscitech:tokencore:1.3.0'
}
```
-### 2) Copy this minimal bootstrap code
+### Maven
-```java
-WalletManager.storage = () -> new File("./keystore");
-WalletManager.scanWallets();
+```xml
+
+
+ jitpack.io
+ https://jitpack.io
+
+
-String password = "change_me";
-Identity identity = Identity.getCurrentIdentity();
-if (identity == null) {
- identity = Identity.createIdentity("default", password, "", Network.MAINNET, Metadata.P2WPKH);
-}
+
+ com.github.galaxyscitech
+ tokencore
+ 1.3.0
+
+```
+
+---
-Wallet wallet = identity.deriveWalletByMnemonics(
- ChainType.ETHEREUM,
- password,
- MnemonicUtil.randomMnemonicCodes());
+## Quick start (runnable)
-System.out.println("Address = " + wallet.getAddress());
+```java
+import org.consenlabs.tokencore.foundation.utils.MnemonicUtil;
+import org.consenlabs.tokencore.wallet.*;
+import org.consenlabs.tokencore.wallet.model.*;
+
+import java.io.File;
+
+public class QuickStart {
+ public static void main(String[] args) {
+ WalletManager.storage = () -> new File("./keystore");
+ WalletManager.scanWallets();
+
+ String password = "UseAStrongPassword_123";
+ Identity identity = Identity.getCurrentIdentity();
+ if (identity == null) {
+ identity = Identity.createIdentity("main", password, "", Network.MAINNET, Metadata.P2WPKH);
+ }
+
+ 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());
+ }
+}
```
-### 3) Verify locally
+---
-```bash
-./gradlew test
-```
+## Common usage
-## Core Features (Recommended Minimum)
+### 1) Import wallet from private key
-For new integrators, keep the initial rollout small:
+```java
+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
+);
+```
-1. **Identity + keystore only** (account generation + secure storage)
-2. **Single chain first** (recommend: ETH or BTC)
-3. **Offline signing only** (avoid online key usage)
-4. **No multi-chain abstraction in v1 API surface**
+### 2) Import wallet from mnemonic
-This reduces integration complexity and speeds up first successful deployment.
+```java
+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
+);
+```
-## Integration
+### 3) Find wallet by mnemonic (BTC-family friendly)
-### Gradle
+```java
+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
+);
+```
-```gradle
-repositories {
- maven { url 'https://jitpack.io' }
-}
-dependencies {
- implementation 'com.github.galaxyscitech:tokencore:1.3.0'
-}
+### 4) Export keystore and recover by keystore
+
+```java
+String keystoreJson = WalletManager.exportKeystore(wallet.getId(), "password123");
+Wallet found = WalletManager.findWalletByKeystore(ChainType.ETHEREUM, keystoreJson, "password123");
```
-### Maven
+---
-```xml
-
-
- jitpack.io
- https://jitpack.io
-
-
+## Security recommendations
-
- com.github.galaxyscitech
- tokencore
- 1.3.0
-
-```
+- 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.
-## Supported Chains
+---
+
+## Typical errors
-| 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 |
+- `password_incorrect`
+- `mnemonic_length_invalid`
+- `mnemonic_word_invalid`
+- `invalid_mnemonic_path`
+- `unsupported_chain`
+- `private_key_address_not_match`
+
+---
-## Build & Test
+## Build and test
```bash
-./gradlew build
./gradlew test
+./gradlew build
```
-## License
-
-This project is licensed under the [GNU General Public License v3.0](LICENSE).
+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(