us.eharning.atomun.mnemonic.spi.electrum.v2.MnemonicUnitSpiImpl.java Source code

Java tutorial

Introduction

Here is the source code for us.eharning.atomun.mnemonic.spi.electrum.v2.MnemonicUnitSpiImpl.java

Source

/*
 * Copyright 2014, 2015 Thomas Harning Jr. <harningt@gmail.com>
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package us.eharning.atomun.mnemonic.spi.electrum.v2;

import com.google.common.base.Charsets;
import com.google.common.base.Converter;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists;
import com.tomgibara.crinch.bits.BitWriter;
import com.tomgibara.crinch.bits.ByteArrayBitWriter;
import us.eharning.atomun.mnemonic.MnemonicAlgorithm;
import us.eharning.atomun.mnemonic.MnemonicUnit;
import us.eharning.atomun.mnemonic.spi.BidirectionalDictionary;
import us.eharning.atomun.mnemonic.spi.MnemonicUnitSpi;

import java.math.BigInteger;
import java.text.Normalizer;
import java.util.Arrays;
import java.util.List;
import javax.annotation.CheckForNull;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable;

/**
 * Internal class to implement the electrum v2 mnemonic details.
 */
@Immutable
class MnemonicUnitSpiImpl extends MnemonicUnitSpi {
    private final BidirectionalDictionary dictionary;

    /**
     * Construct a electrum v2 decoder SPI instance for the given dictionary.
     *
     * @param dictionary
     *         instance to match mnemonic words against.
     */
    public MnemonicUnitSpiImpl(@Nonnull BidirectionalDictionary dictionary) {
        super(MnemonicAlgorithm.ElectrumV2);
        this.dictionary = dictionary;
    }

    /**
     * Convert a sequence of mnemonic word into a byte array for validation and usage.
     *
     * @param dictionary
     *         instance to check for the presence of all words.
     * @param mnemonicWordList
     *         sequence of mnemonic words to map against a dictionary for bit values.
     *
     * @return sequence of bytes based on word list.
     */
    @Nonnull
    private static byte[] mnemonicToBytes(@Nonnull BidirectionalDictionary dictionary,
            @Nonnull List<String> mnemonicWordList) {
        if (fast_is_pow2(dictionary.getSize())) {
            byte[] result = mnemonicToBytesWithBitshift(dictionary, mnemonicWordList);
            byte[] checkResult = mnemonicToBytesWithMultiplication(dictionary, mnemonicWordList);
            if (!Arrays.equals(result, checkResult)) {
                throw new Error(
                        "Mismatched results!" + Arrays.toString(result) + " != " + Arrays.toString(checkResult));
            }
            return result;
        } else {
            return mnemonicToBytesWithMultiplication(dictionary, mnemonicWordList);
        }
    }

    /**
     * Convert a sequence of mnemonic word into a byte array for validation and usage.
     *
     * @param dictionary
     *         instance to check for the presence of all words.
     * @param mnemonicWordList
     *         sequence of mnemonic words to map against a dictionary for bit values.
     *
     * @return sequence of bytes based on word list.
     */
    @Nonnull
    private static byte[] mnemonicToBytesWithBitshift(@Nonnull BidirectionalDictionary dictionary,
            @Nonnull List<String> mnemonicWordList) {
        Converter<String, Integer> reverseConverter = dictionary.reverse();

        final int wordListSize = dictionary.getSize();
        final int unitSize = fast_log2(wordListSize);

        final int totalBits = mnemonicWordList.size() * unitSize;
        final int totalBytes = totalBits >> 3;
        int firstBits = (totalBytes << 3) % unitSize;
        if (firstBits == 0) {
            firstBits = unitSize;
        }

        byte[] result = new byte[(totalBits + 7) >> 3];
        BitWriter bw = new ByteArrayBitWriter(result);

        boolean modified = false;
        int bitsToWrite = firstBits;
        for (int i = mnemonicWordList.size() - 1; i >= 0; i--) {
            String word = mnemonicWordList.get(i);
            /* Find the word index in the wordList. */
            /* Warning suppressed due to word guaranteed non-null */
            //noinspection ConstantConditions
            int index = reverseConverter.convert(word);

            if (index > (1 << bitsToWrite)) {
                bitsToWrite += 8;
                modified = true;
            }

            bw.write(index, bitsToWrite);
            bitsToWrite = unitSize;
        }
        int newStartIndex = 0;
        int newEndIndex = result.length;
        /* If we haven't used the extra space due to mis-identified starter bit-length,
         * trim it off if it was in fact "extra" space.
         */
        if (!modified && totalBytes != result.length) {
            newEndIndex--;
        }
        while (result[newStartIndex] == 0) {
            newStartIndex++;
        }
        if (newStartIndex != 0 || newEndIndex != result.length) {
            byte[] tmp = new byte[newEndIndex - newStartIndex];
            System.arraycopy(result, newStartIndex, tmp, 0, tmp.length);
            result = tmp;
        }
        return result;
    }

    /**
     * Convert a sequence of mnemonic word into a byte array for validation and usage.
     *
     * @param dictionary
     *         instance to check for the presence of all words.
     * @param mnemonicWordList
     *         sequence of mnemonic words to map against a dictionary for bit values.
     *
     * @return sequence of bytes based on word list.
     */
    @Nonnull
    private static byte[] mnemonicToBytesWithMultiplication(@Nonnull BidirectionalDictionary dictionary,
            @Nonnull List<String> mnemonicWordList) {
        Converter<String, Integer> reverseConverter = dictionary.reverse();

        BigInteger total = BigInteger.ZERO;
        BigInteger multiplier = BigInteger.valueOf(dictionary.getSize());
        for (String word : Lists.reverse(mnemonicWordList)) {
            /* Find the word index in the wordList. */
            /* Warning suppressed due to word guaranteed non-null */
            //noinspection ConstantConditions
            int index = reverseConverter.convert(word);

            total = total.multiply(multiplier).add(BigInteger.valueOf(index));
        }

        /* Convert the resultant value to an unsigned byte-array */
        byte[] result = total.toByteArray();
        if (result[0] == 0) {
            byte[] tmp = new byte[result.length - 1];
            System.arraycopy(result, 1, tmp, 0, tmp.length);
            result = tmp;
        }
        return result;
    }

    /**
     * Perform fast determination if positive integer is power of 2.
     *
     * @param value
     *         integer to check if power of 2.
     *
     * @return true if value power of 2.
     */
    private static boolean fast_is_pow2(int value) {
        return value != 0 && 0 == (value & (value - 1));
    }

    /**
     * Perform a fast log2 calculation on an integer known as a positive power of 2.
     *
     * @param value
     *         integer to calculate log2 of
     *
     * @return log2
     */
    private static int fast_log2(int value) {
        int[] bitMask = { 0xAAAAAAAA, 0xCCCCCCCC, 0xF0F0F0F0, 0xFF00FF00, 0xFFFF0000 };
        int result = (value & bitMask[0]) != 0 ? 1 : 0;
        for (int i = 4; i > 0; i--) { // unroll for speed...
            result |= ((value & bitMask[i]) != 0 ? 1 : 0) << i;
        }
        return result;
    }

    /**
     * Utility method to generate a MnemonicUnit wrapping the given sequence and entropy.
     *
     * @param mnemonicSequence
     *         sequence.
     * @param entropy
     *         derived copy of entropy.
     *
     * @return wrapped instance.
     */
    @Nonnull
    public MnemonicUnit build(MnemonicUnit.Builder builder, CharSequence mnemonicSequence, byte[] entropy) {
        return super.build(builder, mnemonicSequence, entropy, null, ImmutableMap.<String, Object>of());
    }

    /**
     * Get the entropy if possible.
     *
     * @param mnemonicSequence
     *         sequence to derive entropy from.
     *
     * @return a derived copy of the entropy byte array.
     */
    @CheckForNull
    @Override
    public byte[] getEntropy(@Nonnull CharSequence mnemonicSequence) {
        List<String> mnemonicWordList = MnemonicUtility.getNormalizedWordList(mnemonicSequence);

        /* Convert the word list into a sequence of booleans representing its bits. */
        return mnemonicToBytes(dictionary, mnemonicWordList);
    }

    /**
     * Get a seed from this mnemonic.
     *
     * @param mnemonicSequence
     *         sequence to derive the seed from.
     * @param password
     *         password to supply for decoding.
     *
     * @return a derived seed.
     */
    @Nonnull
    @Override
    public byte[] getSeed(@Nonnull CharSequence mnemonicSequence, @Nullable CharSequence password) {
        byte[] mnemonicSequenceBytes = Normalizer.normalize(mnemonicSequence, Normalizer.Form.NFKD)
                .getBytes(Charsets.UTF_8);

        /* Normalize the password and get the UTF-8 bytes. */
        String normalizedPassword = "mnemonic";
        if (null != password && 0 != password.length()) {
            normalizedPassword = normalizedPassword + Normalizer.normalize(password, Normalizer.Form.NFKD);
        }
        byte[] passwordBytes = normalizedPassword.getBytes(Charsets.UTF_8);
        return MnemonicUtility.deriveSeed(passwordBytes, mnemonicSequenceBytes);
    }
}