forked from Qortal/qortal
catbref
6 years ago
5 changed files with 2282 additions and 44 deletions
@ -0,0 +1,55 @@ |
|||||||
|
package globalization; |
||||||
|
|
||||||
|
import java.io.File; |
||||||
|
import java.io.FilenameFilter; |
||||||
|
import java.io.IOException; |
||||||
|
import java.nio.file.Files; |
||||||
|
import java.util.Collections; |
||||||
|
import java.util.HashMap; |
||||||
|
import java.util.List; |
||||||
|
import java.util.Map; |
||||||
|
|
||||||
|
import settings.Settings; |
||||||
|
|
||||||
|
/** Providing multi-language BIP39 word lists, downloaded from https://github.com/bitcoin/bips/tree/master/bip-0039 */ |
||||||
|
public class BIP39WordList { |
||||||
|
|
||||||
|
private static BIP39WordList instance; |
||||||
|
|
||||||
|
private static Map<String, List<String>> wordListsByLang; |
||||||
|
|
||||||
|
private BIP39WordList() { |
||||||
|
wordListsByLang = new HashMap<>(); |
||||||
|
|
||||||
|
String path = Settings.getInstance().translationsPath(); |
||||||
|
File dir = new File(path); |
||||||
|
File[] files = dir.listFiles(new FilenameFilter() { |
||||||
|
@Override |
||||||
|
public boolean accept(File dir, String name) { |
||||||
|
return name.startsWith("BIP39."); |
||||||
|
} |
||||||
|
}); |
||||||
|
|
||||||
|
try { |
||||||
|
for (File file : files) { |
||||||
|
String lang = file.getName().substring(6, 8); |
||||||
|
List<String> words = Files.readAllLines(file.toPath()); |
||||||
|
wordListsByLang.put(lang, words); |
||||||
|
} |
||||||
|
} catch (IOException e) { |
||||||
|
throw new RuntimeException("Unable to read BIP39 word list", e); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public static synchronized BIP39WordList getInstance() { |
||||||
|
if (instance == null) |
||||||
|
instance = new BIP39WordList(); |
||||||
|
|
||||||
|
return instance; |
||||||
|
} |
||||||
|
|
||||||
|
public List<String> getByLang(String lang) { |
||||||
|
return Collections.unmodifiableList(wordListsByLang.get(lang)); |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,86 @@ |
|||||||
|
package utils; |
||||||
|
|
||||||
|
import java.util.ArrayList; |
||||||
|
import java.util.List; |
||||||
|
|
||||||
|
import globalization.BIP39WordList; |
||||||
|
|
||||||
|
public class BIP39 { |
||||||
|
|
||||||
|
private static final int BITS_PER_WORD = 11; |
||||||
|
|
||||||
|
/** Convert BIP39 seed phrase to binary form */ |
||||||
|
public static byte[] decode(String[] phraseWords, String lang) { |
||||||
|
if (lang == null) |
||||||
|
lang = "en"; |
||||||
|
|
||||||
|
List<String> wordList = BIP39WordList.getInstance().getByLang(lang); |
||||||
|
if (wordList == null) |
||||||
|
throw new IllegalStateException("BIP39 word list for lang '" + lang + "' unavailable"); |
||||||
|
|
||||||
|
byte[] output = new byte[(phraseWords.length * BITS_PER_WORD + 7) / 8]; |
||||||
|
int byteIndex = 0; |
||||||
|
int bitShift = 3; |
||||||
|
|
||||||
|
for (int i = 0; i < phraseWords.length; ++i) { |
||||||
|
int wordListIndex = wordList.indexOf(phraseWords[i]); |
||||||
|
if (wordListIndex == -1) |
||||||
|
// Word not found
|
||||||
|
return null; |
||||||
|
|
||||||
|
output[byteIndex++] |= (byte) (wordListIndex >> bitShift); |
||||||
|
|
||||||
|
bitShift = 8 - bitShift; |
||||||
|
if (bitShift >= 0) { |
||||||
|
// Leftover fits inside one byte
|
||||||
|
output[byteIndex] |= (byte) ((wordListIndex << bitShift)); |
||||||
|
bitShift = BITS_PER_WORD - bitShift; |
||||||
|
} else { |
||||||
|
// Leftover spread over next two bytes
|
||||||
|
bitShift = 0 - bitShift; |
||||||
|
output[byteIndex++] |= (byte) (wordListIndex >> bitShift); |
||||||
|
|
||||||
|
output[byteIndex] |= (byte) ((wordListIndex << (8 - bitShift))); |
||||||
|
bitShift = bitShift + BITS_PER_WORD - 8; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return output; |
||||||
|
} |
||||||
|
|
||||||
|
/** Convert binary to BIP39 seed phrase */ |
||||||
|
public static String encode(byte[] input, String lang) { |
||||||
|
if (lang == null) |
||||||
|
lang = "en"; |
||||||
|
|
||||||
|
List<String> wordList = BIP39WordList.getInstance().getByLang(lang); |
||||||
|
if (wordList == null) |
||||||
|
throw new IllegalStateException("BIP39 word list for lang '" + lang + "' unavailable"); |
||||||
|
|
||||||
|
List<String> phraseWords = new ArrayList<>(); |
||||||
|
|
||||||
|
int bitMask = 128; // MSB first
|
||||||
|
int byteIndex = 0; |
||||||
|
while (true) { |
||||||
|
int wordListIndex = 0; |
||||||
|
for (int bitCount = 0; bitCount < BITS_PER_WORD; ++bitCount) { |
||||||
|
wordListIndex <<= 1; |
||||||
|
|
||||||
|
if ((input[byteIndex] & bitMask) != 0) |
||||||
|
++wordListIndex; |
||||||
|
|
||||||
|
bitMask >>= 1; |
||||||
|
if (bitMask == 0) { |
||||||
|
bitMask = 128; |
||||||
|
++byteIndex; |
||||||
|
|
||||||
|
if (byteIndex >= input.length) |
||||||
|
return String.join(" ", phraseWords); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
phraseWords.add(wordList.get(wordListIndex)); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
} |
Loading…
Reference in new issue