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

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 160,165评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,720评论 1 298
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,849评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,245评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,596评论 3 288
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,747评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,977评论 2 315
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,708评论 0 204
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,448评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,657评论 2 249
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,141评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,493评论 3 258
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,153评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,108评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,890评论 0 198
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,799评论 2 277
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,685评论 2 272