美文网首页
imtoken 实战化之 助记词校验代码分享

imtoken 实战化之 助记词校验代码分享

作者: 卡哇伊小王子 | 来源:发表于2019-11-29 10:37 被阅读0次

正常来说,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

相关文章

网友评论

      本文标题:imtoken 实战化之 助记词校验代码分享

      本文链接:https://www.haomeiwen.com/subject/atmwwctx.html