From 106ae42ffb8efa441581b8b948cf19b24db0ae34 Mon Sep 17 00:00:00 2001 From: chaozhu Date: Wed, 15 Jan 2025 16:15:32 +0800 Subject: [PATCH 1/3] support mnemonic --- .../TMdrg9hq5u1jKoWAKNASSxyKuzc6ajQRgv.json | 1 + build.gradle | 3 + .../java/org/tron/common/utils/AbiUtil.java | 10 +- src/main/java/org/tron/demo/ECKeyDemo.java | 3 +- src/main/java/org/tron/mnemonic/Mnemonic.java | 211 ++++++++ .../java/org/tron/mnemonic/MnemonicFile.java | 451 ++++++++++++++++++ .../java/org/tron/mnemonic/MnemonicUtils.java | 114 +++++ src/main/java/org/tron/test/Test.java | 9 +- src/main/java/org/tron/walletcli/Client.java | 170 ++++++- .../org/tron/walletcli/WalletApiWrapper.java | 24 +- .../java/org/tron/walletserver/WalletApi.java | 34 +- 11 files changed, 1015 insertions(+), 15 deletions(-) create mode 100644 Mnemonic/TMdrg9hq5u1jKoWAKNASSxyKuzc6ajQRgv.json create mode 100644 src/main/java/org/tron/mnemonic/Mnemonic.java create mode 100644 src/main/java/org/tron/mnemonic/MnemonicFile.java create mode 100644 src/main/java/org/tron/mnemonic/MnemonicUtils.java diff --git a/Mnemonic/TMdrg9hq5u1jKoWAKNASSxyKuzc6ajQRgv.json b/Mnemonic/TMdrg9hq5u1jKoWAKNASSxyKuzc6ajQRgv.json new file mode 100644 index 000000000..dfc04c695 --- /dev/null +++ b/Mnemonic/TMdrg9hq5u1jKoWAKNASSxyKuzc6ajQRgv.json @@ -0,0 +1 @@ +{"address":"TMdrg9hq5u1jKoWAKNASSxyKuzc6ajQRgv","id":"1b752214-16f7-45b3-bd65-d064c91f59a0","version":3,"crypto":{"cipher":"aes-128-ctr","ciphertext":"1a3cb9216008ebec745ad08ea8a62db0b71a0fcd14a3909bbf8fb0ba8715e20cc31224a8418310069d8ab1b17074c541c9797a057b1c66470ae60012a4ea35d54b95de83654bbca77c","cipherparams":{"iv":"0b3e63cb83813eedf396a0bf0a40f01f"},"kdf":"scrypt","kdfparams":{"dklen":32,"n":262144,"p":1,"r":8,"salt":"e66a12cdf0132003c316a51e35866a3859b6a1fe66a47d7e7b4375546e0defb5"},"mac":"ac7e80c2d3f47860a916fee65ad8cce0650f8fa0d2deb9fff0a76c365cdfb9ec"}} \ No newline at end of file diff --git a/build.gradle b/build.gradle index b954b21cf..c79a28279 100644 --- a/build.gradle +++ b/build.gradle @@ -87,6 +87,9 @@ dependencies { implementation group: 'org.jline', name: 'jline', version: '3.25.0' implementation group: 'io.github.tronprotocol', name: 'zksnark-java-sdk', version: '1.0.0' + + implementation group: 'org.bitcoinj', name: 'bitcoinj-core', version: '0.16.2' + implementation group: 'org.web3j', name: 'crypto', version: '4.5.0' } protobuf { diff --git a/src/main/java/org/tron/common/utils/AbiUtil.java b/src/main/java/org/tron/common/utils/AbiUtil.java index dd310fe46..85568c3cd 100644 --- a/src/main/java/org/tron/common/utils/AbiUtil.java +++ b/src/main/java/org/tron/common/utils/AbiUtil.java @@ -419,7 +419,15 @@ public static void main(String[] args) { System.out.println(parseMethod(byteMethod1, bytesValue1)); } - + public static String generateOccupationConstantPrivateKey() { + StringBuilder privateKey = new StringBuilder(); + String baseKey = "1234567890"; + for (int i = 0; i < 6; i++) { + privateKey.append(baseKey); + } + privateKey.append("1234"); + return privateKey.toString(); + } } diff --git a/src/main/java/org/tron/demo/ECKeyDemo.java b/src/main/java/org/tron/demo/ECKeyDemo.java index d0a13292a..786556496 100644 --- a/src/main/java/org/tron/demo/ECKeyDemo.java +++ b/src/main/java/org/tron/demo/ECKeyDemo.java @@ -8,6 +8,7 @@ import org.tron.common.crypto.ECKey; import org.tron.common.crypto.Hash; import org.tron.common.crypto.Sha256Sm3Hash; +import org.tron.common.utils.AbiUtil; import org.tron.common.utils.Base58; import org.tron.common.utils.ByteArray; import org.tron.common.utils.Utils; @@ -80,7 +81,7 @@ private static String private2Address(byte[] privateKey) throws CipherException } public static void main(String[] args) throws CipherException { - String privateKey = "F43EBCC94E6C257EDBE559183D1A8778B2D5A08040902C0F0A77A3343A1D0EA5"; + String privateKey = AbiUtil.generateOccupationConstantPrivateKey(); String address = private2Address(ByteArray.fromHexString(privateKey)); System.out.println("base58Address: " + address); diff --git a/src/main/java/org/tron/mnemonic/Mnemonic.java b/src/main/java/org/tron/mnemonic/Mnemonic.java new file mode 100644 index 000000000..4d458cd42 --- /dev/null +++ b/src/main/java/org/tron/mnemonic/Mnemonic.java @@ -0,0 +1,211 @@ +package org.tron.mnemonic; + +import org.bouncycastle.crypto.digests.SHA256Digest; +import org.bouncycastle.crypto.generators.PKCS5S2ParametersGenerator; +import org.bouncycastle.crypto.generators.SCrypt; +import org.bouncycastle.crypto.params.KeyParameter; +import org.tron.common.crypto.Hash; +import org.tron.common.crypto.SignInterface; +import org.tron.common.utils.ByteArray; +import org.tron.core.exception.CipherException; +import org.tron.keystore.StringUtils; +import org.tron.walletserver.WalletApi; + +import javax.crypto.BadPaddingException; +import javax.crypto.Cipher; +import javax.crypto.IllegalBlockSizeException; +import javax.crypto.NoSuchPaddingException; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.SecretKeySpec; +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.util.Arrays; +import java.util.List; +import java.util.UUID; + +public class Mnemonic { + + private static final int N_LIGHT = 1 << 12; + private static final int P_LIGHT = 6; + + private static final int N_STANDARD = 1 << 18; + private static final int P_STANDARD = 1; + + private static final int R = 8; + private static final int DKLEN = 32; + + private static final int CURRENT_VERSION = 3; + + private static final String CIPHER = "aes-128-ctr"; + static final String AES_128_CTR = "pbkdf2"; + static final String SCRYPT = "scrypt"; + + public static MnemonicFile create(byte[] password, SignInterface ecKeySm2Pair, + String mnemonicWords, int n, int p) + throws CipherException { + byte[] salt = generateRandomBytes(32); + byte[] derivedKey = generateDerivedScryptKey(password, salt, n, R, p, DKLEN); + byte[] encryptKey = Arrays.copyOfRange(derivedKey, 0, 16); + byte[] iv = generateRandomBytes(16); + byte[] mnemonicWordsBytes = mnemonicWords.getBytes(); + byte[] cipherText = performCipherOperation(Cipher.ENCRYPT_MODE, iv, encryptKey, + mnemonicWordsBytes); + byte[] mac = generateMac(derivedKey, cipherText); + return createMnemonicFile(ecKeySm2Pair, cipherText, iv, salt, mac, n, p); + } + + public static MnemonicFile createStandard(byte[] password, SignInterface ecKeySm2Pair, List mnemonicWords) + throws CipherException { + return create(password, ecKeySm2Pair, MnemonicUtils.mnemonicWordsToString(mnemonicWords), N_STANDARD, P_STANDARD); + } + + public static MnemonicFile createLight(byte[] password, SignInterface ecKeySm2Pair, List mnemonicWords) + throws CipherException { + return create(password, ecKeySm2Pair, MnemonicUtils.mnemonicWordsToString(mnemonicWords), N_LIGHT, P_LIGHT); + } + + private static MnemonicFile createMnemonicFile(SignInterface ecKeySm2Pair, + byte[] cipherText, byte[] iv, byte[] salt, + byte[] mac, int n, int p) { + MnemonicFile MnemonicFile = new MnemonicFile(); + MnemonicFile.setAddress(WalletApi.encode58Check(ecKeySm2Pair.getAddress())); + + MnemonicFile.Crypto crypto = new MnemonicFile.Crypto(); + crypto.setCipher(CIPHER); + crypto.setCiphertext(ByteArray.toHexString(cipherText)); + MnemonicFile.setCrypto(crypto); + + MnemonicFile.CipherParams cipherParams = new MnemonicFile.CipherParams(); + cipherParams.setIv(ByteArray.toHexString(iv)); + crypto.setCipherparams(cipherParams); + + crypto.setKdf(SCRYPT); + MnemonicFile.ScryptKdfParams kdfParams = new MnemonicFile.ScryptKdfParams(); + kdfParams.setDklen(DKLEN); + kdfParams.setN(n); + kdfParams.setP(p); + kdfParams.setR(R); + kdfParams.setSalt(ByteArray.toHexString(salt)); + crypto.setKdfparams(kdfParams); + + crypto.setMac(ByteArray.toHexString(mac)); + MnemonicFile.setCrypto(crypto); + MnemonicFile.setId(UUID.randomUUID().toString()); + MnemonicFile.setVersion(CURRENT_VERSION); + + return MnemonicFile; + } + + private static byte[] generateDerivedScryptKey( + byte[] password, byte[] salt, int n, int r, int p, int dkLen) throws CipherException { + return SCrypt.generate(password, salt, n, r, p, dkLen); + } + + private static byte[] generateAes128CtrDerivedKey( + byte[] password, byte[] salt, int c, String prf) throws CipherException { + + if (!prf.equals("hmac-sha256")) { + throw new CipherException("Unsupported prf:" + prf); + } + + PKCS5S2ParametersGenerator gen = new PKCS5S2ParametersGenerator(new SHA256Digest()); + gen.init(password, salt, c); + return ((KeyParameter) gen.generateDerivedParameters(256)).getKey(); + } + + private static byte[] performCipherOperation( + int mode, byte[] iv, byte[] encryptKey, byte[] text) throws CipherException { + + try { + IvParameterSpec ivParameterSpec = new IvParameterSpec(iv); + Cipher cipher = Cipher.getInstance("AES/CTR/NoPadding"); + + SecretKeySpec secretKeySpec = new SecretKeySpec(encryptKey, "AES"); + cipher.init(mode, secretKeySpec, ivParameterSpec); + return cipher.doFinal(text); + } catch (NoSuchPaddingException | NoSuchAlgorithmException + | InvalidAlgorithmParameterException | InvalidKeyException + | BadPaddingException | IllegalBlockSizeException e) { + throw new CipherException("Error performing cipher operation", e); + } + } + + private static byte[] generateMac(byte[] derivedKey, byte[] cipherText) { + byte[] result = new byte[16 + cipherText.length]; + + System.arraycopy(derivedKey, 16, result, 0, 16); + System.arraycopy(cipherText, 0, result, 16, cipherText.length); + + return Hash.sha3(result); + } + + public static byte[] decrypt2MnemonicWordsBytes(byte[] password, MnemonicFile MnemonicFile) + throws CipherException { + validate(MnemonicFile); + MnemonicFile.Crypto crypto = MnemonicFile.getCrypto(); + + byte[] mac = ByteArray.fromHexString(crypto.getMac()); + byte[] iv = ByteArray.fromHexString(crypto.getCipherparams().getIv()); + byte[] cipherText = ByteArray.fromHexString(crypto.getCiphertext()); + byte[] derivedKey; + + MnemonicFile.KdfParams kdfParams = crypto.getKdfparams(); + if (kdfParams instanceof MnemonicFile.ScryptKdfParams) { + MnemonicFile.ScryptKdfParams scryptKdfParams = + (MnemonicFile.ScryptKdfParams) crypto.getKdfparams(); + int dklen = scryptKdfParams.getDklen(); + int n = scryptKdfParams.getN(); + int p = scryptKdfParams.getP(); + int r = scryptKdfParams.getR(); + byte[] salt = ByteArray.fromHexString(scryptKdfParams.getSalt()); + derivedKey = generateDerivedScryptKey(password, salt, n, r, p, dklen); + } else if (kdfParams instanceof MnemonicFile.Aes128CtrKdfParams) { + MnemonicFile.Aes128CtrKdfParams aes128CtrKdfParams = + (MnemonicFile.Aes128CtrKdfParams) crypto.getKdfparams(); + int c = aes128CtrKdfParams.getC(); + String prf = aes128CtrKdfParams.getPrf(); + byte[] salt = ByteArray.fromHexString(aes128CtrKdfParams.getSalt()); + + derivedKey = generateAes128CtrDerivedKey(password, salt, c, prf); + } else { + throw new CipherException("Unable to deserialize params: " + crypto.getKdf()); + } + + byte[] derivedMac = generateMac(derivedKey, cipherText); + + if (!Arrays.equals(derivedMac, mac)) { + throw new CipherException("Invalid password provided"); + } + + byte[] encryptKey = Arrays.copyOfRange(derivedKey, 0, 16); + StringUtils.clear(derivedKey); + byte[] mnemonicWords = performCipherOperation(Cipher.DECRYPT_MODE, iv, encryptKey, cipherText); + StringUtils.clear(encryptKey); + + return mnemonicWords; + } + + static void validate(MnemonicFile MnemonicFile) throws CipherException { + MnemonicFile.Crypto crypto = MnemonicFile.getCrypto(); + if (MnemonicFile.getVersion() != CURRENT_VERSION) { + throw new CipherException("Mnemonic version is not supported"); + } + + if (!crypto.getCipher().equals(CIPHER)) { + throw new CipherException("Mnemonic cipher is not supported"); + } + + if (!crypto.getKdf().equals(AES_128_CTR) && !crypto.getKdf().equals(SCRYPT)) { + throw new CipherException("KDF type is not supported"); + } + } + + public static byte[] generateRandomBytes(int size) { + byte[] bytes = new byte[size]; + new SecureRandom().nextBytes(bytes); + return bytes; + } + +} diff --git a/src/main/java/org/tron/mnemonic/MnemonicFile.java b/src/main/java/org/tron/mnemonic/MnemonicFile.java new file mode 100644 index 000000000..753a88e67 --- /dev/null +++ b/src/main/java/org/tron/mnemonic/MnemonicFile.java @@ -0,0 +1,451 @@ +package org.tron.mnemonic; + +import com.fasterxml.jackson.annotation.JsonSetter; +import com.fasterxml.jackson.annotation.JsonSubTypes; +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; +import java.io.IOException; + +public class MnemonicFile { + private String address; + private MnemonicFile.Crypto crypto; + private String id; + private int version; + + public MnemonicFile() { + } + + public String getAddress() { + return address; + } + + public void setAddress(String address) { + this.address = address; + } + + public MnemonicFile.Crypto getCrypto() { + return crypto; + } + + @JsonSetter("crypto") + public void setCrypto(MnemonicFile.Crypto crypto) { + this.crypto = crypto; + } + + @JsonSetter("Crypto") + public void setCryptoV1(MnemonicFile.Crypto crypto) { + setCrypto(crypto); + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public int getVersion() { + return version; + } + + public void setVersion(int version) { + this.version = version; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof MnemonicFile)) { + return false; + } + + MnemonicFile that = (MnemonicFile) o; + + if (getAddress() != null + ? !getAddress().equals(that.getAddress()) + : that.getAddress() != null) { + return false; + } + if (getCrypto() != null + ? !getCrypto().equals(that.getCrypto()) + : that.getCrypto() != null) { + return false; + } + if (getId() != null + ? !getId().equals(that.getId()) + : that.getId() != null) { + return false; + } + return version == that.version; + } + + @Override + public int hashCode() { + int result = getAddress() != null ? getAddress().hashCode() : 0; + result = 31 * result + (getCrypto() != null ? getCrypto().hashCode() : 0); + result = 31 * result + (getId() != null ? getId().hashCode() : 0); + result = 31 * result + version; + return result; + } + + public static class Crypto { + private String cipher; + private String ciphertext; + private MnemonicFile.CipherParams cipherparams; + + private String kdf; + private MnemonicFile.KdfParams kdfparams; + + private String mac; + + public Crypto() { + } + + public String getCipher() { + return cipher; + } + + public void setCipher(String cipher) { + this.cipher = cipher; + } + + public String getCiphertext() { + return ciphertext; + } + + public void setCiphertext(String ciphertext) { + this.ciphertext = ciphertext; + } + + public MnemonicFile.CipherParams getCipherparams() { + return cipherparams; + } + + public void setCipherparams(MnemonicFile.CipherParams cipherparams) { + this.cipherparams = cipherparams; + } + + public String getKdf() { + return kdf; + } + + public void setKdf(String kdf) { + this.kdf = kdf; + } + + public MnemonicFile.KdfParams getKdfparams() { + return kdfparams; + } + + @JsonTypeInfo( + use = JsonTypeInfo.Id.NAME, + include = JsonTypeInfo.As.EXTERNAL_PROPERTY, + property = "kdf") + @JsonSubTypes({ + @JsonSubTypes.Type(value = MnemonicFile.Aes128CtrKdfParams.class, name = Mnemonic.AES_128_CTR), + @JsonSubTypes.Type(value = MnemonicFile.ScryptKdfParams.class, name = Mnemonic.SCRYPT) + }) + + public void setKdfparams(MnemonicFile.KdfParams kdfparams) { + this.kdfparams = kdfparams; + } + + public String getMac() { + return mac; + } + + public void setMac(String mac) { + this.mac = mac; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof MnemonicFile.Crypto)) { + return false; + } + + MnemonicFile.Crypto that = (MnemonicFile.Crypto) o; + + if (getCipher() != null + ? !getCipher().equals(that.getCipher()) + : that.getCipher() != null) { + return false; + } + if (getCiphertext() != null + ? !getCiphertext().equals(that.getCiphertext()) + : that.getCiphertext() != null) { + return false; + } + if (getCipherparams() != null + ? !getCipherparams().equals(that.getCipherparams()) + : that.getCipherparams() != null) { + return false; + } + if (getKdf() != null + ? !getKdf().equals(that.getKdf()) + : that.getKdf() != null) { + return false; + } + if (getKdfparams() != null + ? !getKdfparams().equals(that.getKdfparams()) + : that.getKdfparams() != null) { + return false; + } + return getMac() != null + ? getMac().equals(that.getMac()) : that.getMac() == null; + } + + @Override + public int hashCode() { + int result = getCipher() != null ? getCipher().hashCode() : 0; + result = 31 * result + (getCiphertext() != null ? getCiphertext().hashCode() : 0); + result = 31 * result + (getCipherparams() != null ? getCipherparams().hashCode() : 0); + result = 31 * result + (getKdf() != null ? getKdf().hashCode() : 0); + result = 31 * result + (getKdfparams() != null ? getKdfparams().hashCode() : 0); + result = 31 * result + (getMac() != null ? getMac().hashCode() : 0); + return result; + } + + } + + public static class CipherParams { + private String iv; + + public CipherParams() { + } + + public String getIv() { + return iv; + } + + public void setIv(String iv) { + this.iv = iv; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof MnemonicFile.CipherParams)) { + return false; + } + + MnemonicFile.CipherParams that = (MnemonicFile.CipherParams) o; + + return getIv() != null + ? getIv().equals(that.getIv()) : that.getIv() == null; + } + + @Override + public int hashCode() { + int result = getIv() != null ? getIv().hashCode() : 0; + return result; + } + + } + + interface KdfParams { + int getDklen(); + + String getSalt(); + } + + public static class Aes128CtrKdfParams implements MnemonicFile.KdfParams { + private int dklen; + private int c; + private String prf; + private String salt; + + public Aes128CtrKdfParams() { + } + + public int getDklen() { + return dklen; + } + + public void setDklen(int dklen) { + this.dklen = dklen; + } + + public int getC() { + return c; + } + + public void setC(int c) { + this.c = c; + } + + public String getPrf() { + return prf; + } + + public void setPrf(String prf) { + this.prf = prf; + } + + public String getSalt() { + return salt; + } + + public void setSalt(String salt) { + this.salt = salt; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof MnemonicFile.Aes128CtrKdfParams)) { + return false; + } + + MnemonicFile.Aes128CtrKdfParams that = (MnemonicFile.Aes128CtrKdfParams) o; + + if (dklen != that.dklen) { + return false; + } + if (c != that.c) { + return false; + } + if (getPrf() != null + ? !getPrf().equals(that.getPrf()) + : that.getPrf() != null) { + return false; + } + return getSalt() != null + ? getSalt().equals(that.getSalt()) : that.getSalt() == null; + } + + @Override + public int hashCode() { + int result = dklen; + result = 31 * result + c; + result = 31 * result + (getPrf() != null ? getPrf().hashCode() : 0); + result = 31 * result + (getSalt() != null ? getSalt().hashCode() : 0); + return result; + } + } + + public static class ScryptKdfParams implements MnemonicFile.KdfParams { + private int dklen; + private int n; + private int p; + private int r; + private String salt; + + public ScryptKdfParams() { + } + + public int getDklen() { + return dklen; + } + + public void setDklen(int dklen) { + this.dklen = dklen; + } + + public int getN() { + return n; + } + + public void setN(int n) { + this.n = n; + } + + public int getP() { + return p; + } + + public void setP(int p) { + this.p = p; + } + + public int getR() { + return r; + } + + public void setR(int r) { + this.r = r; + } + + public String getSalt() { + return salt; + } + + public void setSalt(String salt) { + this.salt = salt; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof MnemonicFile.ScryptKdfParams)) { + return false; + } + + MnemonicFile.ScryptKdfParams that = (MnemonicFile.ScryptKdfParams) o; + + if (dklen != that.dklen) { + return false; + } + if (n != that.n) { + return false; + } + if (p != that.p) { + return false; + } + if (r != that.r) { + return false; + } + return getSalt() != null + ? getSalt().equals(that.getSalt()) : that.getSalt() == null; + } + + @Override + public int hashCode() { + int result = dklen; + result = 31 * result + n; + result = 31 * result + p; + result = 31 * result + r; + result = 31 * result + (getSalt() != null ? getSalt().hashCode() : 0); + return result; + } + } + + static class KdfParamsDeserialiser extends JsonDeserializer { + + @Override + public MnemonicFile.KdfParams deserialize( + JsonParser jsonParser, DeserializationContext deserializationContext) + throws IOException { + + ObjectMapper objectMapper = (ObjectMapper) jsonParser.getCodec(); + ObjectNode root = objectMapper.readTree(jsonParser); + MnemonicFile.KdfParams kdfParams; + + JsonNode n = root.get("n"); + if (n == null) { + kdfParams = objectMapper.convertValue(root, MnemonicFile.Aes128CtrKdfParams.class); + } else { + kdfParams = objectMapper.convertValue(root, MnemonicFile.ScryptKdfParams.class); + } + + return kdfParams; + } + } +} diff --git a/src/main/java/org/tron/mnemonic/MnemonicUtils.java b/src/main/java/org/tron/mnemonic/MnemonicUtils.java new file mode 100644 index 000000000..772037838 --- /dev/null +++ b/src/main/java/org/tron/mnemonic/MnemonicUtils.java @@ -0,0 +1,114 @@ +package org.tron.mnemonic; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.bitcoinj.crypto.MnemonicCode; +import org.tron.common.utils.ByteArray; +import org.tron.core.exception.CipherException; +import org.web3j.crypto.Bip32ECKeyPair; +import org.web3j.crypto.Credentials; + +import java.io.File; +import java.io.IOException; +import java.security.SecureRandom; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +public class MnemonicUtils { + private static final String FilePath = "Mnemonic"; + private static final ObjectMapper objectMapper = new ObjectMapper(); + + static { + objectMapper.configure(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES, true); + objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + } + + public static List generateMnemonic(SecureRandom secureRandom) { + List words = null; + byte[] entropy = new byte[16]; + secureRandom.nextBytes(entropy); + try { + words = MnemonicCode.INSTANCE.toMnemonic(entropy); + } catch (Exception e) { + + } + return words; + } + + public static String mnemonicWordsToString(List mnemonicWords) { + if (mnemonicWords == null || mnemonicWords.isEmpty()) { + return ""; + } + return String.join(" ", mnemonicWords); + } + + public static List stringToMnemonicWords(String mnemonicString) { + if (mnemonicString == null || mnemonicString.trim().isEmpty()) { + return new ArrayList<>(); + } + return Arrays.asList(mnemonicString.trim().split("\\s+")); + } + + public static String store2Keystore(MnemonicFile mnemonicFile) throws IOException { + if (mnemonicFile == null) { + System.out.println("Warning: Store mnemonic failed, mnemonicFile is null !!"); + return null; + } + File file = new File(FilePath); + if (!file.exists()) { + if (!file.mkdir()) { + throw new IOException("Make directory failed!"); + } + } else { + if (!file.isDirectory()) { + if (file.delete()) { + if (!file.mkdir()) { + throw new IOException("Make directory failed!"); + } + } else { + throw new IOException("File exists and can not be deleted!"); + } + } + } + return generateWalletFile(mnemonicFile, file); + } + + public static String generateWalletFile(MnemonicFile walletFile, File destinationDirectory) + throws IOException { + String fileName = getWalletFileName(walletFile); + File destination = new File(destinationDirectory, fileName); + + objectMapper.writeValue(destination, walletFile); + return fileName; + } + + private static String getWalletFileName(MnemonicFile mnemonicFile) { + return mnemonicFile.getAddress() + ".json"; + } + + public static byte[] exportMnemonic(byte[] password, String ownerAddress) throws IOException, CipherException { + File file = new File("Mnemonic/" + ownerAddress + ".json"); + if (!file.exists()) { + System.out.println("mnemonic file of the address: " + ownerAddress + " not exist"); + System.out.println("Please use ImportWalletByMnemonic to import the wallet or RegisterWallet to create a new wallet."); + } + MnemonicFile mnemonicFile = objectMapper.readValue(file, MnemonicFile.class); + return Mnemonic.decrypt2MnemonicWordsBytes(password, mnemonicFile); + } + + public static byte[] getPrivateKeyFromMnemonic(List mnemonics) { + int HARDENED_BIT = 0x80000000; + String mnemonic = String.join(" ", mnemonics); + byte[] seed = org.web3j.crypto.MnemonicUtils.generateSeed(mnemonic, ""); + Bip32ECKeyPair masterKeypair = Bip32ECKeyPair.generateKeyPair(seed); + // m/44'/195'/0'/0/0 + final int[] path = {44 | HARDENED_BIT, 195 | HARDENED_BIT, 0 | HARDENED_BIT, 0, 0}; + Bip32ECKeyPair bip44Keypair = Bip32ECKeyPair.deriveKeyPair(masterKeypair, path); + Credentials credentials = Credentials.create(bip44Keypair); + String privateKey = credentials.getEcKeyPair().getPrivateKey().toString(16); + + return ByteArray.fromHexString(privateKey); + } +} diff --git a/src/main/java/org/tron/test/Test.java b/src/main/java/org/tron/test/Test.java index 946c20a79..836c1c053 100644 --- a/src/main/java/org/tron/test/Test.java +++ b/src/main/java/org/tron/test/Test.java @@ -9,6 +9,7 @@ import org.tron.common.crypto.Sha256Sm3Hash; import org.tron.common.crypto.SignInterface; import org.tron.common.crypto.sm2.SM2; +import org.tron.common.utils.AbiUtil; import org.tron.common.utils.Base58; import org.tron.common.utils.ByteArray; import org.tron.common.utils.Utils; @@ -113,7 +114,7 @@ public static void testECKey() { public static void testVerify() { String hashBytes = "630211D6CA9440639F4965AA24831EB84815AB6BEF11E8BE6962A8540D861339"; - String priKeyBytes = "8E812436A0E3323166E1F0E8BA79E19E217B2C4A53C970D4CCA0CFB1078979DF"; + String priKeyBytes = AbiUtil.generateOccupationConstantPrivateKey(); String sign = "1D89243F93670AA2F209FD1E0BDACA67E327B78FA54D728628F4EBBF6B7917E5BB0642717EC2234D21BEFAA7577D5FC6B4D47C94F2C0618862CD4C9E3C839C464"; ECKey eCkey = null; @@ -132,7 +133,7 @@ public static void testVerify() { public static void testGenKey() { ECKey eCkey = null; - String priKeyHex = "cba92a516ea09f620a16ff7ee95ce0df1d56550a8babe9964981a7144c8a784a"; + String priKeyHex = AbiUtil.generateOccupationConstantPrivateKey(); try { BigInteger priK = new BigInteger(priKeyHex, 16); eCkey = ECKey.fromPrivate(priK); @@ -263,7 +264,7 @@ public static void testSha3() { public static void testGenerateWalletFile() throws CipherException, IOException { String PASSWORD = "Insecure Pa55w0rd"; - String priKeyHex = "cba92a516ea09f620a16ff7ee95ce0df1d56550a8babe9964981a7144c8a784a"; + String priKeyHex = AbiUtil.generateOccupationConstantPrivateKey(); // ECKey eCkey = ECKey.fromPrivate(ByteArray.fromHexString(priKeyHex)); SignInterface sm2 = SM2.fromPrivate(ByteArray.fromHexString(priKeyHex)); File file = new File("out"); @@ -311,7 +312,7 @@ public static void testPasswordStrength() { } public static void interfaceTest() { - String privateKey = "4afbef627636b159614be6e210febd5f14dd6531874fb01ece956516541c41c7"; + String privateKey = AbiUtil.generateOccupationConstantPrivateKey(); SignInterface sm2 = SM2.fromPrivate(ByteArray.fromHexString(privateKey)); String address = WalletApi.encode58Check(sm2.getAddress()); System.out.println(address); diff --git a/src/main/java/org/tron/walletcli/Client.java b/src/main/java/org/tron/walletcli/Client.java index c1c20e361..e788d42ef 100755 --- a/src/main/java/org/tron/walletcli/Client.java +++ b/src/main/java/org/tron/walletcli/Client.java @@ -50,6 +50,7 @@ import org.tron.core.zen.address.PaymentAddress; import org.tron.core.zen.address.SpendingKey; import org.tron.keystore.StringUtils; +import org.tron.mnemonic.MnemonicUtils; import org.tron.protos.Protocol.MarketOrder; import org.tron.protos.Protocol.MarketOrderList; import org.tron.protos.Protocol.MarketOrderPairList; @@ -84,6 +85,7 @@ public class Client { "BackupShieldedTRC20Wallet", "BackupWallet", "BackupWallet2Base64", + "ExportWalletMnemonic", "BroadcastTransaction", "CancelAllUnfreezeV2", "ChangePassword", @@ -162,6 +164,7 @@ public class Client { "ImportShieldedTRC20Wallet", // "ImportShieldedWallet", "ImportWallet", + "ImportWalletByMnemonic", "ImportWalletByBase64", "ListAssetIssue", "ListAssetIssuePaginated", @@ -230,6 +233,7 @@ public class Client { "BackupShieldedTRC20Wallet", "BackupWallet", "BackupWallet2Base64", + "ExportWalletMnemonic", "BroadcastTransaction", "CancelAllUnfreezeV2", "ChangePassword", @@ -309,6 +313,7 @@ public class Client { "ImportShieldedTRC20Wallet", // "ImportShieldedWallet", "ImportWallet", + "ImportWalletByMnemonic", "ImportWalletByBase64", "ListAssetIssue", "ListAssetIssuePaginated", @@ -390,6 +395,107 @@ private byte[] inputPrivateKey() throws IOException { return result; } + private List inputMnemonicWords() throws IOException { + try { + List words = readWordsWithRetry(); + if (words != null) { + return words; + } else { + System.out.println("\nMaximum retry attempts reached, program exiting."); + } + } catch (Exception e) { + System.out.println("An error occurred in the program." + e.getMessage()); + } + return null; + } + + private static List readWordsWithRetry() { + int REQUIRED_WORDS = 12; + int MAX_RETRY = 2; + + int retryCount = 0; + while (retryCount <= MAX_RETRY) { + try { + System.out.printf("%nPlease enter %d words (separated by spaces) [Attempt %d/%d]:%n", + REQUIRED_WORDS, retryCount + 1, MAX_RETRY + 1); + String line = readLine(); + if (line.isEmpty()) { + System.out.println("Error: Input cannot be empty."); + retryCount++; + continue; + } + String[] wordArray = line.split("\\s+"); + if (wordArray.length != REQUIRED_WORDS) { + System.out.printf("Error: Expected %d words, but %d words were entered.", + REQUIRED_WORDS, wordArray.length); + retryCount++; + continue; + } + List validatedWords = validateWords(wordArray); + if (validatedWords != null) { + return validatedWords; + } + retryCount++; + } catch (Exception e) { + System.out.println("Error: " + e.getMessage()); + retryCount++; + } + } + return null; + } + + private static String readLine() { + StringBuilder input = new StringBuilder(); + try { + int c; + boolean isFirstChar = true; + while ((c = System.in.read()) != -1) { + if (c == 13) { // CR (\r) + continue; + } + if (c == 10) { // LF (\n) + if (input.length() > 0) { + break; + } + continue; + } + if (isFirstChar && Character.isWhitespace(c)) { + continue; + } + isFirstChar = false; + input.append((char) c); + } + while (input.length() > 0 && + Character.isWhitespace(input.charAt(input.length() - 1))) { + input.setLength(input.length() - 1); + } + } catch (IOException e) { + System.err.println(e.getMessage()); + } + return input.toString(); + } + + private static List validateWords(String[] words) { + int MIN_WORD_LENGTH = 1; + final int MAX_WORD_LENGTH = 20; + List validatedWords = new ArrayList<>(); + for (int i = 0; i < words.length; i++) { + String word = words[i]; + if (word.length() < MIN_WORD_LENGTH || word.length() > MAX_WORD_LENGTH) { + System.out.printf("Error: The length of the %dth word '%s' is invalid (should be between %d and %d characters).", + i + 1, word, MIN_WORD_LENGTH, MAX_WORD_LENGTH); + return null; + } + if (!word.matches("[a-zA-Z]+")) { + System.out.printf("Error: The %dth word '%s' contains illegal characters (only letters are allowed).", + i + 1, word); + return null; + } + validatedWords.add(word.toLowerCase()); + } + return validatedWords; + } + private byte[] inputPrivateKey64() throws IOException { Decoder decoder = Base64.getDecoder(); byte[] temp = new byte[128]; @@ -429,7 +535,26 @@ private void importWallet() throws CipherException, IOException { char[] password = Utils.inputPassword2Twice(); byte[] priKey = inputPrivateKey(); - String fileName = walletApiWrapper.importWallet(password, priKey); + String fileName = walletApiWrapper.importWallet(password, priKey, null); + StringUtils.clear(password); + StringUtils.clear(priKey); + + if (null == fileName) { + System.out.println("Import wallet failed !!"); + return; + } + System.out.println("Import a wallet successful, keystore file name is " + fileName); + } + + private void importWalletByMnemonic() throws CipherException, IOException { + + char[] password = Utils.inputPassword2Twice(); + List mnemonicWords = inputMnemonicWords(); + + byte[] priKey = MnemonicUtils.getPrivateKeyFromMnemonic(mnemonicWords); + + String fileName = walletApiWrapper.importWallet(password, priKey, mnemonicWords); + mnemonicWords.clear(); StringUtils.clear(password); StringUtils.clear(priKey); @@ -444,7 +569,7 @@ private void importWalletByBase64() throws CipherException, IOException { char[] password = Utils.inputPassword2Twice(); byte[] priKey = inputPrivateKey64(); - String fileName = walletApiWrapper.importWallet(password, priKey); + String fileName = walletApiWrapper.importWallet(password, priKey, null); StringUtils.clear(password); StringUtils.clear(priKey); @@ -518,6 +643,37 @@ private void backupWallet2Base64() throws IOException, CipherException { } } + private void exportWalletMnemonic() throws IOException, CipherException { + byte[] mnemonic = walletApiWrapper.exportWalletMnemonic(); + char[] mnemonicChars = bytesToChars(mnemonic); + if (!ArrayUtils.isEmpty(mnemonic)) { + System.out.println("exportWalletMnemonic successful !!"); + outputMnemonicChars(mnemonicChars); + System.out.println("\n"); + } + StringUtils.clear(mnemonic); + clearChars(mnemonicChars); + } + + private char[] bytesToChars(byte[] bytes) { + char[] chars = new char[bytes.length]; + for (int i = 0; i < bytes.length; i++) { + chars[i] = (char) (bytes[i] & 0xFF); + } + return chars; + } + + private void outputMnemonicChars(char[] mnemonic) { + for (char c : mnemonic) { + System.out.print(c); + } + System.out.println(); + } + + private void clearChars(char[] mnemonic) { + Arrays.fill(mnemonic, '\0'); + } + private void getAddress() { String address = walletApiWrapper.getAddress(); if (address != null) { @@ -2510,7 +2666,7 @@ private void getTransactionInfoByBlockNum(String[] parameters) { } long blockNum = Long.parseLong(parameters[0]); - Optional result = walletApiWrapper.getTransactionInfoByBlockNum(blockNum); + Optional result = WalletApiWrapper.getTransactionInfoByBlockNum(blockNum); if (result.isPresent()) { TransactionInfoList transactionInfoList = result.get(); @@ -4429,6 +4585,10 @@ private void run() { importWallet(); break; } + case "importwalletbymnemonic": { + importWalletByMnemonic(); + break; + } case "importwalletbybase64": { importWalletByBase64(); break; @@ -4473,6 +4633,10 @@ private void run() { backupWallet2Base64(); break; } + case "exportwalletmnemonic": { + exportWalletMnemonic(); + break; + } case "getaddress": { getAddress(); break; diff --git a/src/main/java/org/tron/walletcli/WalletApiWrapper.java b/src/main/java/org/tron/walletcli/WalletApiWrapper.java index fde565fb2..7aac2fe40 100644 --- a/src/main/java/org/tron/walletcli/WalletApiWrapper.java +++ b/src/main/java/org/tron/walletcli/WalletApiWrapper.java @@ -28,6 +28,7 @@ import org.tron.core.zen.address.SpendingKey; import org.tron.keystore.StringUtils; import org.tron.keystore.WalletFile; +import org.tron.mnemonic.MnemonicUtils; import org.tron.protos.Protocol.Account; import org.tron.protos.Protocol.Block; import org.tron.protos.Protocol.ChainParameters; @@ -69,7 +70,7 @@ public String registerWallet(char[] password) throws CipherException, IOExceptio return keystoreName; } - public String importWallet(char[] password, byte[] priKey) throws CipherException, IOException { + public String importWallet(char[] password, byte[] priKey, List mnemonic) throws CipherException, IOException { if (!WalletApi.passwordValid(password)) { return null; } @@ -79,7 +80,7 @@ public String importWallet(char[] password, byte[] priKey) throws CipherExceptio byte[] passwd = StringUtils.char2Byte(password); - WalletFile walletFile = WalletApi.CreateWalletFile(passwd, priKey); + WalletFile walletFile = WalletApi.CreateWalletFile(passwd, priKey, mnemonic); StringUtils.clear(passwd); String keystoreName = WalletApi.store2Keystore(walletFile); @@ -162,6 +163,25 @@ public byte[] backupWallet() throws IOException, CipherException { return privateKey; } + public byte[] exportWalletMnemonic() throws IOException, CipherException { + if (wallet == null || !wallet.isLoginState()) { + wallet = WalletApi.loadWalletFromKeystore(); + if (wallet == null) { + System.out.println("Warning: ExportWalletMnemonic failed, no mnemonic can be exported !!"); + return null; + } + } + + System.out.println("Please input your password."); + char[] password = Utils.inputPassword(false); + byte[] passwd = StringUtils.char2Byte(password); + wallet.checkPassword(passwd); + StringUtils.clear(password); + + String ownerAddress = WalletApi.encode58Check(wallet.getAddress()); + return MnemonicUtils.exportMnemonic(passwd, ownerAddress); + } + public String getAddress() { if (wallet == null || !wallet.isLoginState()) { // System.out.println("Warning: GetAddress failed, Please login first !!"); diff --git a/src/main/java/org/tron/walletserver/WalletApi.java b/src/main/java/org/tron/walletserver/WalletApi.java index 68c10e45d..ec72bac70 100644 --- a/src/main/java/org/tron/walletserver/WalletApi.java +++ b/src/main/java/org/tron/walletserver/WalletApi.java @@ -14,6 +14,7 @@ import java.io.File; import java.io.IOException; import java.io.UnsupportedEncodingException; +import java.security.SecureRandom; import java.util.ArrayList; import java.util.Comparator; import java.util.HashMap; @@ -80,7 +81,9 @@ import org.tron.common.crypto.ECKey; import org.tron.common.crypto.Hash; import org.tron.common.crypto.Sha256Sm3Hash; +import org.tron.common.crypto.SignInterface; import org.tron.common.crypto.sm2.SM2; +import org.tron.common.utils.AbiUtil; import org.tron.common.utils.Base58; import org.tron.common.utils.ByteArray; import org.tron.common.utils.TransactionUtils; @@ -93,6 +96,9 @@ import org.tron.core.exception.CipherException; import org.tron.keystore.CheckStrength; import org.tron.keystore.Credentials; +import org.tron.mnemonic.Mnemonic; +import org.tron.mnemonic.MnemonicFile; +import org.tron.mnemonic.MnemonicUtils; import org.tron.keystore.Wallet; import org.tron.keystore.WalletFile; import org.tron.keystore.WalletUtils; @@ -243,27 +249,47 @@ public static int getRpcVersion() { /** * Creates a new WalletApi with a random ECKey or no ECKey. */ - public static WalletFile CreateWalletFile(byte[] password) throws CipherException { + public static WalletFile CreateWalletFile(byte[] password) throws CipherException, IOException { WalletFile walletFile = null; + SecureRandom secureRandom = Utils.getRandom(); + List mnemonicWords = MnemonicUtils.generateMnemonic(secureRandom); + System.out.println("generateMnemonic words:" + StringUtils.join(mnemonicWords, " ")); + byte[] priKey = MnemonicUtils.getPrivateKeyFromMnemonic(mnemonicWords); + if (isEckey) { - ECKey ecKey = new ECKey(Utils.getRandom()); + ECKey ecKey = new ECKey(priKey, true); walletFile = Wallet.createStandard(password, ecKey); + storeMnemonicWords(password, ecKey, mnemonicWords); } else { - SM2 sm2 = new SM2(Utils.getRandom()); + SM2 sm2 = new SM2(priKey, true); walletFile = Wallet.createStandard(password, sm2); + storeMnemonicWords(password, sm2, mnemonicWords); } + return walletFile; } + public static void storeMnemonicWords(byte[] password, SignInterface ecKeySm2Pair, List mnemonicWords) throws CipherException, IOException { + MnemonicFile mnemonicFile = Mnemonic.createStandard(password, ecKeySm2Pair, mnemonicWords); + String keystoreName = MnemonicUtils.store2Keystore(mnemonicFile); + System.out.println("store mnemonicWords in file :" + keystoreName); + } + // Create Wallet with a pritKey - public static WalletFile CreateWalletFile(byte[] password, byte[] priKey) throws CipherException { + public static WalletFile CreateWalletFile(byte[] password, byte[] priKey, List mnemonicWords) throws CipherException, IOException { WalletFile walletFile = null; if (isEckey) { ECKey ecKey = ECKey.fromPrivate(priKey); walletFile = Wallet.createStandard(password, ecKey); + if (mnemonicWords !=null && !mnemonicWords.isEmpty()) { + storeMnemonicWords(password, ecKey, mnemonicWords); + } } else { SM2 sm2 = SM2.fromPrivate(priKey); walletFile = Wallet.createStandard(password, sm2); + if (mnemonicWords !=null && !mnemonicWords.isEmpty()) { + storeMnemonicWords(password, sm2, mnemonicWords); + } } return walletFile; } From 3b244c15d863e04f46f6258b2c46679789224e56 Mon Sep 17 00:00:00 2001 From: chaozhu Date: Thu, 16 Jan 2025 00:19:15 +0800 Subject: [PATCH 2/3] support mnemonic --- README.md | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 8008661d6..481524502 100644 --- a/README.md +++ b/README.md @@ -100,7 +100,8 @@ For more information on a specific command, just type the command on terminal wh | [WithdrawExpireUnfreeze](#How-to-freezev2) | [CancelAllUnfreezeV2](#How-to-freezev2) |[GetDelegatedResourceV2](#How-to-freezev2) | | [GetDelegatedResourceAccountIndexV2](#How-to-freezev2) | [GetCanDelegatedMaxSize](#How-to-freezev2) | [GetAvailableUnfreezeCount](#How-to-freezev2) | | [GetCanWithdrawUnfreezeAmount](#How-to-freezev2) |[GetBandwidthPrices](#Get-resource-prices-and-memo-fee) | [GetEnergyPrices](#Get-resource-prices-and-memo-fee)| -| [GetMemoFee](#Get-resource-prices-and-memo-fee) ||| +| [GetMemoFee](#Get-resource-prices-and-memo-fee) |[ImportWalletByMnemonic](#import-and-export-mnemonic) | [ExportWalletMnemonic](#import-and-export-mnemonic) | + Type any one of the listed commands, to display how-to tips. @@ -1506,6 +1507,32 @@ wallet> getMemoFee "prices": "0:0,1675492680000:1000000" } ``` +## import and export mnemonic + >ImportWalletByMnemonic +>Import wallet, you need to set a password, mnemonic + +Example: +```console +wallet> ImportWalletByMnemonic +Please input password. +password: +Please input password again. +password: +Please enter 12 words (separated by spaces) [Attempt 1/3]: +``` + + >ExportWalletMnemonic +>export mnemonic of the address in the wallet + +Example: +```console +wallet> ExportWalletMnemonic +Please input your password. +password: +exportWalletMnemonic successful !! +a*ert tw*st co*rect mat*er pa*s g*ther p*t p*sition s*op em*ty coc*nut aband*n +``` + ## Wallet related commands From 04e852e09eb93418789ae91d86095f7b173cc659 Mon Sep 17 00:00:00 2001 From: chaozhu Date: Thu, 16 Jan 2025 10:46:31 +0800 Subject: [PATCH 3/3] support mnemonic --- src/main/java/org/tron/walletserver/WalletApi.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/tron/walletserver/WalletApi.java b/src/main/java/org/tron/walletserver/WalletApi.java index ec72bac70..18f784d06 100644 --- a/src/main/java/org/tron/walletserver/WalletApi.java +++ b/src/main/java/org/tron/walletserver/WalletApi.java @@ -253,7 +253,7 @@ public static WalletFile CreateWalletFile(byte[] password) throws CipherExceptio WalletFile walletFile = null; SecureRandom secureRandom = Utils.getRandom(); List mnemonicWords = MnemonicUtils.generateMnemonic(secureRandom); - System.out.println("generateMnemonic words:" + StringUtils.join(mnemonicWords, " ")); + //System.out.println("generateMnemonic words:" + StringUtils.join(mnemonicWords, " ")); byte[] priKey = MnemonicUtils.getPrivateKeyFromMnemonic(mnemonicWords); if (isEckey) { @@ -272,7 +272,7 @@ public static WalletFile CreateWalletFile(byte[] password) throws CipherExceptio public static void storeMnemonicWords(byte[] password, SignInterface ecKeySm2Pair, List mnemonicWords) throws CipherException, IOException { MnemonicFile mnemonicFile = Mnemonic.createStandard(password, ecKeySm2Pair, mnemonicWords); String keystoreName = MnemonicUtils.store2Keystore(mnemonicFile); - System.out.println("store mnemonicWords in file :" + keystoreName); + //System.out.println("store mnemonicWords in file :" + keystoreName); } // Create Wallet with a pritKey