Simplify mnemonic generation

This commit is contained in:
M66B 2022-03-07 09:39:06 +01:00
parent e7e2c69c10
commit 15476c78a5
28 changed files with 14 additions and 7607 deletions

View File

@ -41,4 +41,3 @@ FairEmail uses:
* [Cousine font](https://fonts.google.com/specimen/Cousine). By Steve Matteson. [Apache License 2.0](https://fonts.google.com/specimen/Cousine#license).
* [Lato font](https://fonts.google.com/specimen/Lato). By Łukasz Dziedzic. [Apache License 2.0](https://fonts.google.com/specimen/Lato#license).
* [Caladea font](https://fonts.google.com/specimen/Caladea). By Andrés Torresi, Carolina Giovanolli. [Apache License 2.0](https://fonts.google.com/specimen/Caladea#license).
* [BIP39](https://github.com/NovaCrypto/BIP39). Copyright (C) 2017-2019 Alan Evans, NovaCrypto. [GNU General Public License v3.0](https://github.com/NovaCrypto/BIP39/blob/master/LICENCE.txt).

View File

@ -41,4 +41,3 @@ FairEmail uses:
* [Cousine font](https://fonts.google.com/specimen/Cousine). By Steve Matteson. [Apache License 2.0](https://fonts.google.com/specimen/Cousine#license).
* [Lato font](https://fonts.google.com/specimen/Lato). By Łukasz Dziedzic. [Apache License 2.0](https://fonts.google.com/specimen/Lato#license).
* [Caladea font](https://fonts.google.com/specimen/Caladea). By Andrés Torresi, Carolina Giovanolli. [Apache License 2.0](https://fonts.google.com/specimen/Caladea#license).
* [BIP39](https://github.com/NovaCrypto/BIP39). Copyright (C) 2017-2019 Alan Evans, NovaCrypto. [GNU General Public License v3.0](https://github.com/NovaCrypto/BIP39/blob/master/LICENCE.txt).

View File

@ -1,45 +1,7 @@
/*
* BIP39 library, a Java implementation of BIP39
* Copyright (C) 2017-2019 Alan Evans, NovaCrypto
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* Original source: https://github.com/NovaCrypto/BIP39
* You can contact the authors via github issues.
*/
package eu.faircode.email;
package io.github.novacrypto.bip39.wordlists;
import io.github.novacrypto.bip39.WordList;
/**
* Source: https://github.com/bitcoin/bips/blob/master/bip-0039/english.txt
*/
public enum English implements WordList {
INSTANCE;
@Override
public String getWord(final int index) {
return words[index];
}
@Override
public char getSpace() {
return ' ';
}
private final static String[] words = new String[]{
public class BIP39 {
public final static String[] words = new String[]{
"abandon",
"ability",
"able",
@ -2089,4 +2051,4 @@ public enum English implements WordList {
"zone",
"zoo"
};
}
}

View File

@ -58,13 +58,12 @@ import androidx.preference.PreferenceManager;
import androidx.webkit.WebViewFeature;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.security.SecureRandom;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import io.github.novacrypto.bip39.MnemonicGenerator;
import io.github.novacrypto.bip39.Words;
import io.github.novacrypto.bip39.wordlists.English;
import java.util.ArrayList;
import java.util.List;
public class FragmentOptionsPrivacy extends FragmentBase implements SharedPreferences.OnSharedPreferenceChangeListener {
private SwitchCompat swConfirmLinks;
@ -107,6 +106,8 @@ public class FragmentOptionsPrivacy extends FragmentBase implements SharedPrefer
private Group grpSafeBrowsing;
private final static int BIP39_WORDS = 6;
private final static String[] RESET_OPTIONS = new String[]{
"confirm_links", "check_links_dbl", "browse_links",
"confirm_images", "ask_images", "html_always_images", "confirm_html", "ask_html",
@ -463,12 +464,11 @@ public class FragmentOptionsPrivacy extends FragmentBase implements SharedPrefer
@Override
public void onCheckedChanged(CompoundButton compoundButton, boolean checked) {
if (checked) {
// https://github.com/NovaCrypto/BIP39
StringBuilder sb = new StringBuilder();
byte[] entropy = new byte[Words.TWELVE.byteLength()];
new SecureRandom().nextBytes(entropy);
new MnemonicGenerator(English.INSTANCE).createMnemonic(entropy, sb::append);
String mnemonic = sb.toString();
List<String> words = new ArrayList<>();
SecureRandom rnd = new SecureRandom();
for (int i = 0; i < BIP39_WORDS; i++)
words.add(BIP39.words[rnd.nextInt(2048)]);
String mnemonic = TextUtils.join(" ", words);
prefs.edit().putString("wipe_mnemonic", mnemonic).apply();
tvMnemonic.setText(mnemonic);

View File

@ -1,42 +0,0 @@
/*
* BIP39 library, a Java implementation of BIP39
* Copyright (C) 2017-2019 Alan Evans, NovaCrypto
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* Original source: https://github.com/NovaCrypto/BIP39
* You can contact the authors via github issues.
*/
package io.github.novacrypto.bip39;
import java.util.Comparator;
enum CharSequenceComparators implements Comparator<CharSequence> {
ALPHABETICAL {
@Override
public int compare(final CharSequence o1, final CharSequence o2) {
final int length1 = o1.length();
final int length2 = o2.length();
final int length = Math.min(length1, length2);
for (int i = 0; i < length; i++) {
final int compare = Character.compare(o1.charAt(i), o2.charAt(i));
if (compare != 0) return compare;
}
return Integer.compare(length1, length2);
}
}
}

View File

@ -1,58 +0,0 @@
/*
* BIP39 library, a Java implementation of BIP39
* Copyright (C) 2017-2019 Alan Evans, NovaCrypto
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* Original source: https://github.com/NovaCrypto/BIP39
* You can contact the authors via github issues.
*/
package io.github.novacrypto.bip39;
final class ByteUtils {
static int next11Bits(byte[] bytes, int offset) {
final int skip = offset / 8;
final int lowerBitsToRemove = (3 * 8 - 11) - (offset % 8);
return (((int) bytes[skip] & 0xff) << 16 |
((int) bytes[skip + 1] & 0xff) << 8 |
(lowerBitsToRemove < 8
? ((int) bytes[skip + 2] & 0xff)
: 0)) >> lowerBitsToRemove & (1 << 11) - 1;
}
static void writeNext11(byte[] bytes, int value, int offset) {
int skip = offset / 8;
int bitSkip = offset % 8;
{//byte 0
byte firstValue = bytes[skip];
byte toWrite = (byte) (value >> (3 + bitSkip));
bytes[skip] = (byte) (firstValue | toWrite);
}
{//byte 1
byte valueInByte = bytes[skip + 1];
final int i = 5 - bitSkip;
byte toWrite = (byte) (i > 0 ? (value << i) : (value >> -i));
bytes[skip + 1] = (byte) (valueInByte | toWrite);
}
if (bitSkip >= 6) {//byte 2
byte valueInByte = bytes[skip + 2];
byte toWrite = (byte) (value << 13 - bitSkip);
bytes[skip + 2] = (byte) (valueInByte | toWrite);
}
}
}

View File

@ -1,51 +0,0 @@
/*
* BIP39 library, a Java implementation of BIP39
* Copyright (C) 2017-2019 Alan Evans, NovaCrypto
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* Original source: https://github.com/NovaCrypto/BIP39
* You can contact the authors via github issues.
*/
package io.github.novacrypto.bip39;
import java.util.LinkedList;
import java.util.List;
final class CharSequenceSplitter {
private final char separator1;
private final char separator2;
CharSequenceSplitter(final char separator1, final char separator2) {
this.separator1 = separator1;
this.separator2 = separator2;
}
List<CharSequence> split(final CharSequence charSequence) {
final LinkedList<CharSequence> list = new LinkedList<>();
int start = 0;
final int length = charSequence.length();
for (int i = 0; i < length; i++) {
final char c = charSequence.charAt(i);
if (c == separator1 || c == separator2) {
list.add(charSequence.subSequence(start, i));
start = i + 1;
}
}
list.add(charSequence.subSequence(start, length));
return list;
}
}

View File

@ -1,66 +0,0 @@
/*
* BIP39 library, a Java implementation of BIP39
* Copyright (C) 2017-2019 Alan Evans, NovaCrypto
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* Original source: https://github.com/NovaCrypto/BIP39
* You can contact the authors via github issues.
*/
package io.github.novacrypto.bip39;
import io.github.novacrypto.toruntime.CheckedExceptionToRuntime;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
import static io.github.novacrypto.toruntime.CheckedExceptionToRuntime.toRuntime;
/**
* Not available in all Java implementations, for example will not find the implementation before Android API 26+.
* See https://developer.android.com/reference/javax/crypto/SecretKeyFactory.html for more details.
*/
public enum JavaxPBKDF2WithHmacSHA512 implements PBKDF2WithHmacSHA512 {
INSTANCE;
private SecretKeyFactory skf = getPbkdf2WithHmacSHA512();
@Override
public byte[] hash(char[] chars, byte[] salt) {
final PBEKeySpec spec = new PBEKeySpec(chars, salt, 2048, 512);
final byte[] encoded = generateSecretKey(spec).getEncoded();
spec.clearPassword();
return encoded;
}
private SecretKey generateSecretKey(final PBEKeySpec spec) {
return toRuntime(new CheckedExceptionToRuntime.Func<SecretKey>() {
@Override
public SecretKey run() throws Exception {
return skf.generateSecret(spec);
}
});
}
private static SecretKeyFactory getPbkdf2WithHmacSHA512() {
return toRuntime(new CheckedExceptionToRuntime.Func<SecretKeyFactory>() {
@Override
public SecretKeyFactory run() throws Exception {
return SecretKeyFactory.getInstance("PBKDF2WithHmacSHA512");
}
});
}
}

View File

@ -1,141 +0,0 @@
/*
* BIP39 library, a Java implementation of BIP39
* Copyright (C) 2017-2019 Alan Evans, NovaCrypto
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* Original source: https://github.com/NovaCrypto/BIP39
* You can contact the authors via github issues.
*/
package io.github.novacrypto.bip39;
import java.util.Arrays;
import static io.github.novacrypto.bip39.ByteUtils.next11Bits;
import static io.github.novacrypto.hashing.Sha256.sha256;
/**
* Generates mnemonics from entropy.
*/
public final class MnemonicGenerator {
private final WordList wordList;
/**
* Create a generator using the given word list.
*
* @param wordList A known ordered list of 2048 words to select from.
*/
public MnemonicGenerator(final WordList wordList) {
this.wordList = wordList;
}
public interface Target {
void append(final CharSequence string);
}
/**
* Create a mnemonic from the word list given the entropy.
*
* @param entropyHex 128-256 bits of hex entropy, number of bits must also be divisible by 32
* @param target Where to write the mnemonic to
*/
public void createMnemonic(
final CharSequence entropyHex,
final Target target) {
final int length = entropyHex.length();
if (length % 2 != 0)
throw new RuntimeException("Length of hex chars must be divisible by 2");
final byte[] entropy = new byte[length / 2];
try {
for (int i = 0, j = 0; i < length; i += 2, j++) {
entropy[j] = (byte) (parseHex(entropyHex.charAt(i)) << 4 | parseHex(entropyHex.charAt(i + 1)));
}
createMnemonic(entropy, target);
} finally {
Arrays.fill(entropy, (byte) 0);
}
}
/**
* Create a mnemonic from the word list given the entropy.
*
* @param entropy 128-256 bits of entropy, number of bits must also be divisible by 32
* @param target Where to write the mnemonic to
*/
public void createMnemonic(
final byte[] entropy,
final Target target) {
final int[] wordIndexes = wordIndexes(entropy);
try {
createMnemonic(wordIndexes, target);
} finally {
Arrays.fill(wordIndexes, 0);
}
}
private void createMnemonic(
final int[] wordIndexes,
final Target target) {
final String space = String.valueOf(wordList.getSpace());
for (int i = 0; i < wordIndexes.length; i++) {
if (i > 0) target.append(space);
target.append(wordList.getWord(wordIndexes[i]));
}
}
private static int[] wordIndexes(byte[] entropy) {
final int ent = entropy.length * 8;
entropyLengthPreChecks(ent);
final byte[] entropyWithChecksum = Arrays.copyOf(entropy, entropy.length + 1);
entropyWithChecksum[entropy.length] = firstByteOfSha256(entropy);
//checksum length
final int cs = ent / 32;
//mnemonic length
final int ms = (ent + cs) / 11;
//get the indexes into the word list
final int[] wordIndexes = new int[ms];
for (int i = 0, wi = 0; wi < ms; i += 11, wi++) {
wordIndexes[wi] = next11Bits(entropyWithChecksum, i);
}
return wordIndexes;
}
static byte firstByteOfSha256(final byte[] entropy) {
final byte[] hash = sha256(entropy);
final byte firstByte = hash[0];
Arrays.fill(hash, (byte) 0);
return firstByte;
}
private static void entropyLengthPreChecks(final int ent) {
if (ent < 128)
throw new RuntimeException("Entropy too low, 128-256 bits allowed");
if (ent > 256)
throw new RuntimeException("Entropy too high, 128-256 bits allowed");
if (ent % 32 > 0)
throw new RuntimeException("Number of entropy bits must be divisible by 32");
}
private static int parseHex(char c) {
if (c >= '0' && c <= '9') return c - '0';
if (c >= 'a' && c <= 'f') return (c - 'a') + 10;
if (c >= 'A' && c <= 'F') return (c - 'A') + 10;
throw new RuntimeException("Invalid hex char '" + c + '\'');
}
}

View File

@ -1,190 +0,0 @@
/*
* BIP39 library, a Java implementation of BIP39
* Copyright (C) 2017-2019 Alan Evans, NovaCrypto
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* Original source: https://github.com/NovaCrypto/BIP39
* You can contact the authors via github issues.
*/
package io.github.novacrypto.bip39;
import io.github.novacrypto.bip39.Validation.InvalidChecksumException;
import io.github.novacrypto.bip39.Validation.InvalidWordCountException;
import io.github.novacrypto.bip39.Validation.UnexpectedWhiteSpaceException;
import io.github.novacrypto.bip39.Validation.WordNotFoundException;
import java.util.Arrays;
import java.util.Collection;
import java.util.Comparator;
import static io.github.novacrypto.bip39.MnemonicGenerator.firstByteOfSha256;
import static io.github.novacrypto.bip39.Normalization.normalizeNFKD;
/**
* Contains function for validating Mnemonics against the BIP0039 spec.
*/
public final class MnemonicValidator {
private final WordAndIndex[] words;
private final CharSequenceSplitter charSequenceSplitter;
private final NFKDNormalizer normalizer;
private MnemonicValidator(final WordList wordList) {
normalizer = new WordListMapNormalization(wordList);
words = new WordAndIndex[1 << 11];
for (int i = 0; i < 1 << 11; i++) {
words[i] = new WordAndIndex(i, wordList.getWord(i));
}
charSequenceSplitter = new CharSequenceSplitter(wordList.getSpace(), normalizeNFKD(wordList.getSpace()));
Arrays.sort(words, wordListSortOrder);
}
/**
* Get a Mnemonic validator for the given word list.
* No normalization is currently performed, this is an open issue: https://github.com/NovaCrypto/BIP39/issues/13
*
* @param wordList A WordList implementation
* @return A validator
*/
public static MnemonicValidator ofWordList(final WordList wordList) {
return new MnemonicValidator(wordList);
}
/**
* Check that the supplied mnemonic fits the BIP0039 spec.
*
* @param mnemonic The memorable list of words
* @throws InvalidChecksumException If the last bytes don't match the expected last bytes
* @throws InvalidWordCountException If the number of words is not a multiple of 3, 24 or fewer
* @throws WordNotFoundException If a word in the mnemonic is not present in the word list
* @throws UnexpectedWhiteSpaceException Occurs if one of the supplied words is empty, e.g. a double space
*/
public void validate(final CharSequence mnemonic) throws
InvalidChecksumException,
InvalidWordCountException,
WordNotFoundException,
UnexpectedWhiteSpaceException {
validate(charSequenceSplitter.split(mnemonic));
}
/**
* Check that the supplied mnemonic fits the BIP0039 spec.
* <p>
* The purpose of this method overload is to avoid constructing a mnemonic String if you have gathered a list of
* words from the user.
*
* @param mnemonic The memorable list of words
* @throws InvalidChecksumException If the last bytes don't match the expected last bytes
* @throws InvalidWordCountException If the number of words is not a multiple of 3, 24 or fewer
* @throws WordNotFoundException If a word in the mnemonic is not present in the word list
* @throws UnexpectedWhiteSpaceException Occurs if one of the supplied words is empty
*/
public void validate(final Collection<? extends CharSequence> mnemonic) throws
InvalidChecksumException,
InvalidWordCountException,
WordNotFoundException,
UnexpectedWhiteSpaceException {
final int[] wordIndexes = findWordIndexes(mnemonic);
try {
validate(wordIndexes);
} finally {
Arrays.fill(wordIndexes, 0);
}
}
private static void validate(final int[] wordIndexes) throws
InvalidWordCountException,
InvalidChecksumException {
final int ms = wordIndexes.length;
final int entPlusCs = ms * 11;
final int ent = (entPlusCs * 32) / 33;
final int cs = ent / 32;
if (entPlusCs != ent + cs)
throw new InvalidWordCountException();
final byte[] entropyWithChecksum = new byte[(entPlusCs + 7) / 8];
wordIndexesToEntropyWithCheckSum(wordIndexes, entropyWithChecksum);
Arrays.fill(wordIndexes, 0);
final byte[] entropy = Arrays.copyOf(entropyWithChecksum, entropyWithChecksum.length - 1);
final byte lastByte = entropyWithChecksum[entropyWithChecksum.length - 1];
Arrays.fill(entropyWithChecksum, (byte) 0);
final byte sha = firstByteOfSha256(entropy);
final byte mask = maskOfFirstNBits(cs);
if (((sha ^ lastByte) & mask) != 0)
throw new InvalidChecksumException();
}
private int[] findWordIndexes(final Collection<? extends CharSequence> split) throws
UnexpectedWhiteSpaceException,
WordNotFoundException {
final int ms = split.size();
final int[] result = new int[ms];
int i = 0;
for (final CharSequence buffer : split) {
if (buffer.length() == 0) {
throw new UnexpectedWhiteSpaceException();
}
result[i++] = findWordIndex(buffer);
}
return result;
}
private int findWordIndex(final CharSequence buffer) throws WordNotFoundException {
final WordAndIndex key = new WordAndIndex(-1, buffer);
final int index = Arrays.binarySearch(words, key, wordListSortOrder);
if (index < 0) {
final int insertionPoint = -index - 1;
int suggestion = insertionPoint == 0 ? insertionPoint : insertionPoint - 1;
if (suggestion + 1 == words.length) suggestion--;
throw new WordNotFoundException(buffer, words[suggestion].word, words[suggestion + 1].word);
}
return words[index].index;
}
private static void wordIndexesToEntropyWithCheckSum(final int[] wordIndexes, final byte[] entropyWithChecksum) {
for (int i = 0, bi = 0; i < wordIndexes.length; i++, bi += 11) {
ByteUtils.writeNext11(entropyWithChecksum, wordIndexes[i], bi);
}
}
private static byte maskOfFirstNBits(final int n) {
return (byte) ~((1 << (8 - n)) - 1);
}
private static final Comparator<WordAndIndex> wordListSortOrder = new Comparator<WordAndIndex>() {
@Override
public int compare(final WordAndIndex o1, final WordAndIndex o2) {
return CharSequenceComparators.ALPHABETICAL.compare(o1.normalized, o2.normalized);
}
};
private class WordAndIndex {
final CharSequence word;
final String normalized;
final int index;
WordAndIndex(final int i, final CharSequence word) {
this.word = word;
normalized = normalizer.normalize(word);
index = i;
}
}
}

View File

@ -1,27 +0,0 @@
/*
* BIP39 library, a Java implementation of BIP39
* Copyright (C) 2017-2019 Alan Evans, NovaCrypto
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* Original source: https://github.com/NovaCrypto/BIP39
* You can contact the authors via github issues.
*/
package io.github.novacrypto.bip39;
public interface NFKDNormalizer {
String normalize(CharSequence charSequence);
}

View File

@ -1,34 +0,0 @@
/*
* BIP39 library, a Java implementation of BIP39
* Copyright (C) 2017-2019 Alan Evans, NovaCrypto
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* Original source: https://github.com/NovaCrypto/BIP39
* You can contact the authors via github issues.
*/
package io.github.novacrypto.bip39;
import java.text.Normalizer;
final class Normalization {
static String normalizeNFKD(final String string) {
return Normalizer.normalize(string, Normalizer.Form.NFKD);
}
static char normalizeNFKD(final char c) {
return normalizeNFKD("" + c).charAt(0);
}
}

View File

@ -1,27 +0,0 @@
/*
* BIP39 library, a Java implementation of BIP39
* Copyright (C) 2017-2019 Alan Evans, NovaCrypto
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* Original source: https://github.com/NovaCrypto/BIP39
* You can contact the authors via github issues.
*/
package io.github.novacrypto.bip39;
public interface PBKDF2WithHmacSHA512 {
byte[] hash(final char[] chars, final byte[] salt);
}

View File

@ -1,111 +0,0 @@
/*
* BIP39 library, a Java implementation of BIP39
* Copyright (C) 2017-2019 Alan Evans, NovaCrypto
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* Original source: https://github.com/NovaCrypto/BIP39
* You can contact the authors via github issues.
*/
package io.github.novacrypto.bip39;
import io.github.novacrypto.toruntime.CheckedExceptionToRuntime;
import java.util.Arrays;
import static io.github.novacrypto.bip39.Normalization.normalizeNFKD;
import static io.github.novacrypto.toruntime.CheckedExceptionToRuntime.toRuntime;
/**
* Contains function for generating seeds from a Mnemonic and Passphrase.
*/
public final class SeedCalculator {
private final byte[] fixedSalt = getUtf8Bytes("mnemonic");
private final PBKDF2WithHmacSHA512 hashAlgorithm;
public SeedCalculator(final PBKDF2WithHmacSHA512 hashAlgorithm) {
this.hashAlgorithm = hashAlgorithm;
}
/**
* Creates a seed calculator using {@link SpongyCastlePBKDF2WithHmacSHA512} which is the most compatible.
* Use {@link SeedCalculator#SeedCalculator(PBKDF2WithHmacSHA512)} to supply another.
*/
public SeedCalculator() {
this(SpongyCastlePBKDF2WithHmacSHA512.INSTANCE);
}
/**
* Calculate the seed given a mnemonic and corresponding passphrase.
* The phrase is not checked for validity here, for that use a {@link MnemonicValidator}.
* <p>
* Due to normalization, these need to be {@link String}, and not {@link CharSequence}, this is an open issue:
* https://github.com/NovaCrypto/BIP39/issues/7
* <p>
* If you have a list of words selected from a word list, you can use {@link #withWordsFromWordList} then
* {@link SeedCalculatorByWordListLookUp#calculateSeed}
*
* @param mnemonic The memorable list of words
* @param passphrase An optional passphrase, use "" if not required
* @return a seed for HD wallet generation
*/
public byte[] calculateSeed(final String mnemonic, final String passphrase) {
final char[] chars = normalizeNFKD(mnemonic).toCharArray();
try {
return calculateSeed(chars, passphrase);
} finally {
Arrays.fill(chars, '\0');
}
}
byte[] calculateSeed(final char[] mnemonicChars, final String passphrase) {
final String normalizedPassphrase = normalizeNFKD(passphrase);
final byte[] salt2 = getUtf8Bytes(normalizedPassphrase);
final byte[] salt = combine(fixedSalt, salt2);
clear(salt2);
final byte[] encoded = hash(mnemonicChars, salt);
clear(salt);
return encoded;
}
public SeedCalculatorByWordListLookUp withWordsFromWordList(final WordList wordList) {
return new SeedCalculatorByWordListLookUp(this, wordList);
}
private static byte[] combine(final byte[] array1, final byte[] array2) {
final byte[] bytes = new byte[array1.length + array2.length];
System.arraycopy(array1, 0, bytes, 0, array1.length);
System.arraycopy(array2, 0, bytes, array1.length, bytes.length - array1.length);
return bytes;
}
private static void clear(final byte[] salt) {
Arrays.fill(salt, (byte) 0);
}
private byte[] hash(final char[] chars, final byte[] salt) {
return hashAlgorithm.hash(chars, salt);
}
private static byte[] getUtf8Bytes(final String string) {
return toRuntime(new CheckedExceptionToRuntime.Func<byte[]>() {
@Override
public byte[] run() throws Exception {
return string.getBytes("UTF-8");
}
});
}
}

View File

@ -1,89 +0,0 @@
/*
* BIP39 library, a Java implementation of BIP39
* Copyright (C) 2017-2019 Alan Evans, NovaCrypto
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* Original source: https://github.com/NovaCrypto/BIP39
* You can contact the authors via github issues.
*/
package io.github.novacrypto.bip39;
import java.util.*;
public final class SeedCalculatorByWordListLookUp {
private final SeedCalculator seedCalculator;
private final Map<CharSequence, char[]> map = new HashMap<>();
private final NFKDNormalizer normalizer;
SeedCalculatorByWordListLookUp(final SeedCalculator seedCalculator, final WordList wordList) {
this.seedCalculator = seedCalculator;
normalizer = new WordListMapNormalization(wordList);
for (int i = 0; i < 1 << 11; i++) {
final String word = normalizer.normalize(wordList.getWord(i));
map.put(word, word.toCharArray());
}
}
/**
* Calculate the seed given a mnemonic and corresponding passphrase.
* The phrase is not checked for validity here, for that use a {@link MnemonicValidator}.
* <p>
* The purpose of this method is to avoid constructing a mnemonic String if you have gathered a list of
* words from the user and also to avoid having to normalize it, all words in the {@link WordList} are normalized
* instead.
* <p>
* Due to normalization, the passphrase still needs to be {@link String}, and not {@link CharSequence}, this is an
* open issue: https://github.com/NovaCrypto/BIP39/issues/7
*
* @param mnemonic The memorable list of words, ideally selected from the word list that was supplied while creating this object.
* @param passphrase An optional passphrase, use "" if not required
* @return a seed for HD wallet generation
*/
public byte[] calculateSeed(final Collection<? extends CharSequence> mnemonic, final String passphrase) {
final int words = mnemonic.size();
final char[][] chars = new char[words][];
final List<char[]> toClear = new LinkedList<>();
int count = 0;
int wordIndex = 0;
for (final CharSequence word : mnemonic) {
char[] wordChars = map.get(normalizer.normalize(word));
if (wordChars == null) {
wordChars = normalizer.normalize(word).toCharArray();
toClear.add(wordChars);
}
chars[wordIndex++] = wordChars;
count += wordChars.length;
}
count += words - 1;
final char[] mnemonicChars = new char[count];
try {
int index = 0;
for (int i = 0; i < chars.length; i++) {
System.arraycopy(chars[i], 0, mnemonicChars, index, chars[i].length);
index += chars[i].length;
if (i < chars.length - 1) {
mnemonicChars[index++] = ' ';
}
}
return seedCalculator.calculateSeed(mnemonicChars, passphrase);
} finally {
Arrays.fill(mnemonicChars, '\0');
Arrays.fill(chars, null);
for (final char[] charsToClear : toClear)
Arrays.fill(charsToClear, '\0');
}
}
}

View File

@ -1,42 +0,0 @@
/*
* BIP39 library, a Java implementation of BIP39
* Copyright (C) 2017-2019 Alan Evans, NovaCrypto
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* Original source: https://github.com/NovaCrypto/BIP39
* You can contact the authors via github issues.
*/
package io.github.novacrypto.bip39;
import org.bouncycastle.crypto.PBEParametersGenerator;
import org.bouncycastle.crypto.digests.SHA512Digest;
import org.bouncycastle.crypto.generators.PKCS5S2ParametersGenerator;
import org.bouncycastle.crypto.params.KeyParameter;
/**
* This implementation is useful for older Java implementations, for example it is suitable for all Android API levels.
*/
public enum SpongyCastlePBKDF2WithHmacSHA512 implements PBKDF2WithHmacSHA512 {
INSTANCE;
@Override
public byte[] hash(char[] chars, byte[] salt) {
PKCS5S2ParametersGenerator generator = new PKCS5S2ParametersGenerator(new SHA512Digest());
generator.init(PBEParametersGenerator.PKCS5PasswordToUTF8Bytes(chars), salt, 2048);
KeyParameter key = (KeyParameter) generator.generateDerivedMacParameters(512);
return key.getKey();
}
}

View File

@ -1,28 +0,0 @@
/*
* BIP39 library, a Java implementation of BIP39
* Copyright (C) 2017-2019 Alan Evans, NovaCrypto
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* Original source: https://github.com/NovaCrypto/BIP39
* You can contact the authors via github issues.
*/
package io.github.novacrypto.bip39.Validation;
public final class InvalidChecksumException extends Exception {
public InvalidChecksumException() {
super("Invalid checksum");
}
}

View File

@ -1,28 +0,0 @@
/*
* BIP39 library, a Java implementation of BIP39
* Copyright (C) 2017-2019 Alan Evans, NovaCrypto
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* Original source: https://github.com/NovaCrypto/BIP39
* You can contact the authors via github issues.
*/
package io.github.novacrypto.bip39.Validation;
public final class InvalidWordCountException extends Exception {
public InvalidWordCountException() {
super("Not a correct number of words");
}
}

View File

@ -1,28 +0,0 @@
/*
* BIP39 library, a Java implementation of BIP39
* Copyright (C) 2017-2019 Alan Evans, NovaCrypto
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* Original source: https://github.com/NovaCrypto/BIP39
* You can contact the authors via github issues.
*/
package io.github.novacrypto.bip39.Validation;
public final class UnexpectedWhiteSpaceException extends Exception {
public UnexpectedWhiteSpaceException() {
super("Unexpected whitespace");
}
}

View File

@ -1,54 +0,0 @@
/*
* BIP39 library, a Java implementation of BIP39
* Copyright (C) 2017-2019 Alan Evans, NovaCrypto
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* Original source: https://github.com/NovaCrypto/BIP39
* You can contact the authors via github issues.
*/
package io.github.novacrypto.bip39.Validation;
public final class WordNotFoundException extends Exception {
private final CharSequence word;
private final CharSequence suggestion1;
private final CharSequence suggestion2;
public WordNotFoundException(
final CharSequence word,
final CharSequence suggestion1,
final CharSequence suggestion2) {
super(String.format(
"Word not found in word list \"%s\", suggestions \"%s\", \"%s\"",
word,
suggestion1,
suggestion2));
this.word = word;
this.suggestion1 = suggestion1;
this.suggestion2 = suggestion2;
}
public CharSequence getWord() {
return word;
}
public CharSequence getSuggestion1() {
return suggestion1;
}
public CharSequence getSuggestion2() {
return suggestion2;
}
}

View File

@ -1,40 +0,0 @@
/*
* BIP39 library, a Java implementation of BIP39
* Copyright (C) 2017-2019 Alan Evans, NovaCrypto
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* Original source: https://github.com/NovaCrypto/BIP39
* You can contact the authors via github issues.
*/
package io.github.novacrypto.bip39;
public interface WordList {
/**
* Get a word in the word list.
*
* @param index Index of word in the word list [0..2047] inclusive.
* @return the word from the list.
*/
String getWord(final int index);
/**
* Get the space character for this language.
*
* @return a whitespace character.
*/
char getSpace();
}

View File

@ -1,48 +0,0 @@
/*
* BIP39 library, a Java implementation of BIP39
* Copyright (C) 2017-2019 Alan Evans, NovaCrypto
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* Original source: https://github.com/NovaCrypto/BIP39
* You can contact the authors via github issues.
*/
package io.github.novacrypto.bip39;
import java.text.Normalizer;
import java.util.HashMap;
import java.util.Map;
class WordListMapNormalization implements NFKDNormalizer {
private final Map<CharSequence, String> normalizedMap = new HashMap<>();
WordListMapNormalization(final WordList wordList) {
for (int i = 0; i < 1 << 11; i++) {
final String word = wordList.getWord(i);
final String normalized = Normalizer.normalize(word, Normalizer.Form.NFKD);
normalizedMap.put(word, normalized);
normalizedMap.put(normalized, normalized);
normalizedMap.put(Normalizer.normalize(word, Normalizer.Form.NFC), normalized);
}
}
@Override
public String normalize(final CharSequence charSequence) {
final String normalized = normalizedMap.get(charSequence);
if (normalized != null)
return normalized;
return Normalizer.normalize(charSequence, Normalizer.Form.NFKD);
}
}

View File

@ -1,44 +0,0 @@
/*
* BIP39 library, a Java implementation of BIP39
* Copyright (C) 2017-2019 Alan Evans, NovaCrypto
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* Original source: https://github.com/NovaCrypto/BIP39
* You can contact the authors via github issues.
*/
package io.github.novacrypto.bip39;
public enum Words {
TWELVE(128),
FIFTEEN(160),
EIGHTEEN(192),
TWENTY_ONE(224),
TWENTY_FOUR(256);
private final int bitLength;
Words(int bitLength) {
this.bitLength = bitLength;
}
public int bitLength() {
return bitLength;
}
public int byteLength() {
return bitLength / 8;
}
}

View File

@ -1,65 +0,0 @@
/*
* SHA-256 library
*
* Copyright (C) 2017-2022 Alan Evans, NovaCrypto
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* Original source: https://github.com/NovaCrypto/SHA256
* You can contact the authors via github issues.
*/
package io.github.novacrypto.hashing;
import io.github.novacrypto.toruntime.CheckedExceptionToRuntime;
import java.security.MessageDigest;
import static io.github.novacrypto.toruntime.CheckedExceptionToRuntime.toRuntime;
public final class Sha256 {
Sha256() {
}
public static byte[] sha256(final byte[] bytes) {
return sha256(bytes, 0, bytes.length);
}
public static byte[] sha256(final byte[] bytes, final int offset, final int length) {
final MessageDigest digest = sha256();
digest.update(bytes, offset, length);
return digest.digest();
}
public static byte[] sha256Twice(final byte[] bytes) {
return sha256Twice(bytes, 0, bytes.length);
}
public static byte[] sha256Twice(final byte[] bytes, final int offset, final int length) {
final MessageDigest digest = sha256();
digest.update(bytes, offset, length);
digest.update(digest.digest());
return digest.digest();
}
private static MessageDigest sha256() {
return toRuntime(new CheckedExceptionToRuntime.Func<MessageDigest>() {
@Override
public MessageDigest run() throws Exception {
return MessageDigest.getInstance("SHA-256");
}
});
}
}

View File

@ -1,64 +0,0 @@
/*
* ToRuntime library, Java promotions of checked exceptions to runtime exceptions
* Copyright (C) 2017-2022 Alan Evans, NovaCrypto
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* Original source: https://github.com/NovaCrypto/ToRuntime
* You can contact the authors via github issues.
*/
package io.github.novacrypto.toruntime;
/**
* Promotes any exceptions thrown to {@link RuntimeException}
*/
public final class CheckedExceptionToRuntime {
public interface Func<T> {
T run() throws Exception;
}
public interface Action {
void run() throws Exception;
}
/**
* Promotes any exceptions thrown to {@link RuntimeException}
*
* @param function Function to run
* @param <T> Return type
* @return returns the result of the function
*/
public static <T> T toRuntime(final Func<T> function) {
try {
return function.run();
} catch (final Exception e) {
throw new RuntimeException(e);
}
}
/**
* Promotes any exceptions thrown to {@link RuntimeException}
*
* @param function Function to run
*/
public static void toRuntime(final Action function) {
try {
function.run();
} catch (final Exception e) {
throw new RuntimeException(e);
}
}
}