正常来说,imToken钱包提供三种导入方式,分别是助记词、keystore和私钥。于是我建议他用keystore或者私钥导入。
没想到这位简友只有助记词导入的方式,而且更致命的是,当初他并没有备份keystore和私钥。
这就不好玩儿了,我心里暗想他可能与这笔FTN说再见了,因为在加密货币的世界法则里,一旦助记词、keystore和私钥丢失,那这份财产将基本被宣判无法找回。
这位简友刚刚接触区块链和数字货币,如果钱包丢失的话,很可能会极大地打击到他探索的积极性,我试图想一些其他可能的办法。
无奈之下,我让他如果信得过我的话,把助记词发给我看一看,因为当时我怀疑他可能弄混了助记词和私钥。
他很大方,直接发了过来,我看了一下,确实是助记词。那就没办法了,我正想劝他放弃这个钱包,不过眼睛一下子注意到了倒数第二个单词:
drsign
印象中这好像不是一个单词,为了确定,我又上词典查了一遍,确实没有这个单词拼写。
但imToken的助记词向来是12个拼写完整且正确的英文单词,不可能出现这样的词汇,唯一的可能就是这位简友当时抄写错了。
考虑到键盘上“r”与“e”紧挨着,我建议他试试“design”。
package com.qy.emt.utils.MnemonicUtil;
import java.util.List;
public class MnemonicUtil {
static Stringmessage ="import success";
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 ListrandomMnemonicCodes() {
return toMnemonicCodes(NumericUtil.generateRandomBytes(16));
}
private static ListtoMnemonicCodes(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 com.qy.emt.utils.MnemonicUtil;
import com.google.common.base.Strings;
import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.security.SecureRandom;
import java.util.Arrays;
import java.util.regex.Pattern;
public class NumericUtil {
private final static SecureRandomSECURE_RANDOM =new SecureRandom();
private static final StringHEX_PREFIX ="0x";
public static byte[]generateRandomBytes(int size) {
byte[] bytes =new byte[size];
SECURE_RANDOM.nextBytes(bytes);
return bytes;
}
public static boolean isValidHex(String value) {
if (value ==null) {
return false;
}
if (value.startsWith("0x") || value.startsWith("0X")) {
value = value.substring(2, value.length());
}
if (value.length() ==0 || value.length() %2 !=0) {
return false;
}
String pattern ="[0-9a-fA-F]+";
return Pattern.matches(pattern, value);
// If TestRpc resolves the following issue, we can reinstate this code
// https://github.com/ethereumjs/testrpc/issues/220
// if (value.length() > 3 && value.charAt(2) == '0') {
// return false;
// }
}
public static StringcleanHexPrefix(String input) {
if (hasHexPrefix(input)) {
return input.substring(2);
}else {
return input;
}
}
public static StringprependHexPrefix(String input) {
if (input.length() >1 && !hasHexPrefix(input)) {
return HEX_PREFIX + input;
}else {
return input;
}
}
private static boolean hasHexPrefix(String input) {
return input.length() >1 && input.charAt(0) =='0' && input.charAt(1) =='x';
}
public static BigIntegerbytesToBigInteger(byte[] value, int offset, int length) {
return bytesToBigInteger((Arrays.copyOfRange(value, offset, offset + length)));
}
public static BigIntegerbytesToBigInteger(byte[] value) {
return new BigInteger(1, value);
}
public static BigIntegerhexToBigInteger(String hexValue) {
String cleanValue =cleanHexPrefix(hexValue);
return new BigInteger(cleanValue, 16);
}
public static StringbigIntegerToHex(BigInteger value) {
return value.toString(16);
}
public static StringbigIntegerToHexWithZeroPadded(BigInteger value, int size) {
String result =bigIntegerToHex(value);
int length = result.length();
if (length > size) {
throw new UnsupportedOperationException(
"Value " + result +"is larger then length " + size);
}else if (value.signum() <0) {
throw new UnsupportedOperationException("Value cannot be negative");
}
if (length < size) {
result = Strings.repeat("0", size - length) + result;
}
return result;
}
public static byte[]bigIntegerToBytesWithZeroPadded(BigInteger value, int length) {
byte[] result =new byte[length];
byte[] bytes = value.toByteArray();
int bytesLength;
int srcOffset;
if (bytes[0] ==0) {
bytesLength = bytes.length -1;
srcOffset =1;
}else {
bytesLength = bytes.length;
srcOffset =0;
}
if (bytesLength > length) {
throw new RuntimeException("Input is too large to put in byte array of size " + length);
}
int destOffset = length - bytesLength;
System.arraycopy(bytes, srcOffset, result, destOffset, bytesLength);
return result;
}
public static byte[]hexToBytes(String input) {
String cleanInput =cleanHexPrefix(input);
int len = cleanInput.length();
if (len ==0) {
return new byte[]{};
}
byte[] data;
int startIdx;
if (len %2 !=0) {
data =new byte[(len /2) +1];
data[0] = (byte) Character.digit(cleanInput.charAt(0), 16);
startIdx =1;
}else {
data =new byte[len /2];
startIdx =0;
}
for (int i = startIdx; i < len; i +=2) {
data[(i +1) /2] = (byte) ((Character.digit(cleanInput.charAt(i), 16) <<4)
+ Character.digit(cleanInput.charAt(i +1), 16));
}
return data;
}
public static byte[]hexToBytesLittleEndian(String input) {
byte[] bytes =hexToBytes(input);
if (ByteOrder.nativeOrder() == ByteOrder.LITTLE_ENDIAN) {
return bytes;
}
int middle = bytes.length /2;
for (int i =0; i < middle; i++) {
byte b = bytes[i];
bytes[i] = bytes[bytes.length -1 - i];
bytes[bytes.length -1 - i] = b;
}
return bytes;
}
public static byte[]reverseBytes(byte[] bytes) {
int middle = bytes.length /2;
for (int i =0; i < middle; i++) {
byte b = bytes[i];
bytes[i] = bytes[bytes.length -1 - i];
bytes[bytes.length -1 - i] = b;
}
return bytes;
}
public static StringbytesToHex(byte[] input) {
StringBuilder stringBuilder =new StringBuilder();
if (input.length ==0) {
return "";
}
for (byte anInput : input) {
stringBuilder.append(String.format("%02x", anInput));
}
return stringBuilder.toString();
}
public static StringbeBigEndianHex(String hex) {
if (ByteOrder.nativeOrder() == ByteOrder.BIG_ENDIAN) {
return hex;
}
return reverseHex(hex);
}
public static StringbeLittleEndianHex(String hex) {
if (ByteOrder.nativeOrder() == ByteOrder.LITTLE_ENDIAN) {
return hex;
}
return reverseHex(hex);
}
private static StringreverseHex(String hex) {
byte[] bytes =hexToBytes(hex);
bytes =reverseBytes(bytes);
return bytesToHex(bytes);
}
public static int bytesToInt(byte[] bytes) {
return ByteBuffer.wrap(bytes).getInt();
}
public static byte[]intToBytes(int intValue) {
byte[] intBytes = ByteBuffer.allocate(4).putInt(intValue).array();
int zeroLen =0;
for (byte b : intBytes) {
if (b !=0) {
break;
}
zeroLen++;
}
if (zeroLen ==4) {
zeroLen =3;
}
return Arrays.copyOfRange(intBytes, zeroLen, intBytes.length);
}
}
-------------
package com.qy.emt.utils.MnemonicUtil;
import com.google.common.base.Stopwatch;
import org.bitcoinj.core.Sha256Hash;
import org.bitcoinj.core.Utils;
import org.bitcoinj.crypto.MnemonicException;
import org.bitcoinj.crypto.PBKDF2SHA512;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.security.MessageDigest;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import static com.google.common.base.Preconditions.checkNotNull;
import static org.bitcoinj.core.Utils.HEX;
public class MnemonicCode {
private static final Loggerlog = LoggerFactory.getLogger(MnemonicCode.class);
private ArrayListwordList;
private static final StringBIP39_ENGLISH_RESOURCE_NAME ="/assets/mnemonic.txt";
private static final StringBIP39_ENGLISH_SHA256 ="ad90bf3beb7b0eb7e5acd74727dc0da96e0a280a258354e7293fb7e211ac03db";
/** UNIX time for when the BIP39 standard was finalised. This can be used as a default seed birthday. */
public static long BIP39_STANDARDISATION_TIME_SECS =1381276800;
private static final int PBKDF2_ROUNDS =2048;
public static MnemonicCodeINSTANCE;
static {
try {
INSTANCE =new MnemonicCode();
}catch (FileNotFoundException e) {
// We expect failure on Android. The developer has to set INSTANCE themselves.
if (!Utils.isAndroidRuntime())
log.error("Could not find word list", e);
}catch (IOException e) {
log.error("Failed to load word list", e);
}
}
/** Initialise from the included word list. Won't work on Android. */
public MnemonicCode()throws IOException {
this(openDefaultWords(), BIP39_ENGLISH_SHA256);
}
private static InputStreamopenDefaultWords()throws IOException {
InputStream stream = MnemonicCode.class.getResourceAsStream(BIP39_ENGLISH_RESOURCE_NAME);
if (stream ==null)
throw new FileNotFoundException(BIP39_ENGLISH_RESOURCE_NAME);
return stream;
}
/**
* Creates an MnemonicCode object, initializing with words read from the supplied input stream. If a wordListDigest
* is supplied the digest of the words will be checked.
*/
public MnemonicCode(InputStream wordstream, String wordListDigest)throws IOException, IllegalArgumentException {
BufferedReader br =new BufferedReader(new InputStreamReader(wordstream, "UTF-8"));
this.wordList =new ArrayList(2048);
MessageDigest md = Sha256Hash.newDigest();
String word;
while ((word = br.readLine()) !=null) {
md.update(word.getBytes());
this.wordList.add(word);
}
br.close();
if (this.wordList.size() !=2048)
throw new IllegalArgumentException("input stream did not contain 2048 words");
// If a wordListDigest is supplied check to make sure it matches.
if (wordListDigest !=null) {
byte[] digest = md.digest();
String hexdigest =HEX.encode(digest);
if (!hexdigest.equals(wordListDigest))
throw new IllegalArgumentException("wordlist digest mismatch");
}
}
/**
* Gets the word list this code uses.
*/
public ListgetWordList() {
return wordList;
}
/**
* Convert mnemonic word list to seed.
*/
public static byte[]toSeed(List words, String passphrase) {
checkNotNull(passphrase, "A null passphrase is not allowed.");
// To create binary seed from mnemonic, we use PBKDF2 function
// with mnemonic sentence (in UTF-8) used as a password and
// string "mnemonic" + passphrase (again in UTF-8) used as a
// salt. Iteration count is set to 4096 and HMAC-SHA512 is
// used as a pseudo-random function. Desired length of the
// derived key is 512 bits (= 64 bytes).
//
String pass = Utils.join(words);
String salt ="mnemonic" + passphrase;
final Stopwatch watch = Stopwatch.createStarted();
byte[] seed = PBKDF2SHA512.derive(pass, salt, PBKDF2_ROUNDS, 64);
watch.stop();
log.info("PBKDF2 took {}", watch);
return seed;
}
/**
* Convert mnemonic word list to original entropy value.
*/
public byte[]toEntropy(List words)throws MnemonicException.MnemonicLengthException, MnemonicException.MnemonicWordException, MnemonicException.MnemonicChecksumException {
if (words.size() %3 >0)
throw new MnemonicException.MnemonicLengthException("Word list size must be multiple of three words.");
if (words.size() ==0)
throw new MnemonicException.MnemonicLengthException("Word list is empty.");
// Look up all the words in the list and construct the
// concatenation of the original entropy and the checksum.
//
int concatLenBits = words.size() *11;
boolean[] concatBits =new boolean[concatLenBits];
int wordindex =0;
for (String word : words) {
// Find the words index in the wordlist.
int ndx = Collections.binarySearch(this.wordList, word);
if (ndx <0)
throw new MnemonicException.MnemonicWordException(word);
// Set the next 11 bits to the value of the index.
for (int ii =0; ii <11; ++ii)
concatBits[(wordindex *11) + ii] = (ndx & (1 << (10 - ii))) !=0;
++wordindex;
}
int checksumLengthBits = concatLenBits /33;
int entropyLengthBits = concatLenBits - checksumLengthBits;
// Extract original entropy as bytes.
byte[] entropy =new byte[entropyLengthBits /8];
for (int ii =0; ii < entropy.length; ++ii)
for (int jj =0; jj <8; ++jj)
if (concatBits[(ii *8) + jj])
entropy[ii] |=1 << (7 - jj);
// Take the digest of the entropy.
byte[] hash = Sha256Hash.hash(entropy);
boolean[] hashBits = bytesToBits(hash);
// Check all the checksum bits.
for (int i =0; i < checksumLengthBits; ++i)
if (concatBits[entropyLengthBits + i] != hashBits[i])
throw new MnemonicException.MnemonicChecksumException();
return entropy;
}
/**
* Convert entropy data to mnemonic word list.
*/
public List toMnemonic(byte[] entropy)throws MnemonicException.MnemonicLengthException {
if (entropy.length %4 >0)
throw new MnemonicException.MnemonicLengthException("Entropy length not multiple of 32 bits.");
if (entropy.length ==0)
throw new MnemonicException.MnemonicLengthException("Entropy is empty.");
// We take initial entropy of ENT bits and compute its
// checksum by taking first ENT / 32 bits of its SHA256 hash.
byte[] hash = Sha256Hash.hash(entropy);
boolean[] hashBits =bytesToBits(hash);
boolean[] entropyBits =bytesToBits(entropy);
int checksumLengthBits = entropyBits.length /32;
// We append these bits to the end of the initial entropy.
boolean[] concatBits =new boolean[entropyBits.length + checksumLengthBits];
System.arraycopy(entropyBits, 0, concatBits, 0, entropyBits.length);
System.arraycopy(hashBits, 0, concatBits, entropyBits.length, checksumLengthBits);
// Next we take these concatenated bits and split them into
// groups of 11 bits. Each group encodes number from 0-2047
// which is a position in a wordlist. We convert numbers into
// words and use joined words as mnemonic sentence.
ArrayList words =new ArrayList();
int nwords = concatBits.length /11;
for (int i =0; i < nwords; ++i) {
int index =0;
for (int j =0; j <11; ++j) {
index <<=1;
if (concatBits[(i *11) + j])
index |=0x1;
}
words.add(this.wordList.get(index));
}
return words;
}
/**
* Check to see if a mnemonic word list is valid.
*/
public void check(List words)throws MnemonicException {
toEntropy(words);
}
private static boolean[] bytesToBits(byte[] data) {
boolean[] bits =new boolean[data.length *8];
for (int i =0; i < data.length; ++i)
for (int j =0; j <8; ++j)
bits[(i *8) + j] = (data[i] & (1 << (7 - j))) !=0;
return bits;
}
}
欢迎线下交流 wx:dwl-1591293009
网友评论