Я пишу Java-реализацию BIP39 для Биткойн. Пока мой код способен правильно генерировать случайно сгенерированную мнемоническую фразу. Однако при преобразовании мнемонической фразы из 12 слов в 512-разрядное начальное значение результирующее значение не совпадает с результатами из BIP39 Ian Coleman Tool .
Для начала объект SecureRandom генерирует случайное 512-битное значение энтропии (ENT). Значение ENT хэшируется с использованием SHA256 для вычисления значения контрольной суммы (CS), которая является первыми 4 битами хэша. Контрольная сумма соединяется до конца ENT, чтобы дать ENT_CS. ENT_CS разбивается на секции по 11 битов каждая, и соответствующее целочисленное значение из 11 битов используется в качестве номера индекса для получения слова из списка слов. Это порождает мою мнемоническую фразу из 12 слов. Пока что все шаги до этого момента соответствуют ожидаемым результатам вышеупомянутого инструмента BIP39.
Для создания Seed я использую PBKDF2 с HmacSHA512, устанавливая итерации на 2048, а размер ключа - на 512 бит (64 байта). Я проверил мою реализацию PBKDF2 по этим Векторам испытаний , реализации пакета Google «crypto» и реализации NovaCrypto Java BIP39. Мнемонические слова, исключая разделители, используются в качестве входных данных вместе с солью "mnemonic"+password
в соответствии со спецификациями Bitcoin Core BIP39 .
Функция PBKDF2
public static byte[] PBKDF2(String mnemonic, String salt) {
try {
byte[] fixedSalt = ("mnemonic"+salt).getBytes(StandardCharsets.UTF_8);
KeySpec spec = new PBEKeySpec(mnemonic.toCharArray(), fixedSalt, 2048, 512);
SecretKeyFactory f = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA512");
return f.generateSecret(spec).getEncoded();
} catch (NoSuchAlgorithmException | InvalidKeySpecException ex) {
throw new RuntimeException(ex);
}
}
Создать мнемоническую функцию
public static String[] generateMnemonic() {
// Generate 128-bit Random Number for Entropy
byte[] ENT = getEntropy();
// Hash the Entropy value
byte[] HASH = SHA256(ENT);
// Copy first 4 bits of Hash as Checksum
boolean[] CS = Arrays.copyOfRange(bytesToBits(HASH), 0, 4);
// Add Checksum to the end of Entropy bits
boolean[] ENT_CS = Arrays.copyOf(bytesToBits(ENT), bytesToBits(ENT).length + CS.length);
System.arraycopy(CS, 0, ENT_CS, bytesToBits(ENT).length, CS.length);
// Split ENT_CS into groups of 11 bits and creates String array for
// mnemonicWords
String[] mnemonicWords = new String[12];
for (int i = 0; i < 12; i++) {
boolean[] numBits = Arrays.copyOfRange(ENT_CS, i * 11, i * 11 + 11);
mnemonicWords[i] = wordList.get(bitsToInt(numBits));
}
return mnemonicWords;
}
Вспомогательные функции
// Returns randomly generated, 16-byte number
public static byte[] getEntropy() {
byte[] ent = new byte[16];
sr.nextBytes(ent);
return ent;
}
// Returns bit representation of byte array
public 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;
}
// Returns hex string from byte array
private final static char[] hexArray = "0123456789ABCDEF".toCharArray();
public static String bytesToHex(byte[] bytes) {
char[] hexChars = new char[bytes.length * 2];
for (int j = 0; j < bytes.length; j++) {
int v = bytes[j] & 0xFF;
hexChars[j * 2] = hexArray[v >>> 4];
hexChars[j * 2 + 1] = hexArray[v & 0x0F];
}
return new String(hexChars);
}
// Returns SHA256 hash of input data
public static byte[] SHA256(byte[] data) {
try {
MessageDigest digest = MessageDigest.getInstance("SHA-256");
System.out.println(Arrays.toString(data));
return digest.digest(data);
} catch (NoSuchAlgorithmException ex) {
throw new RuntimeException(ex);
}
}
// Returns int value of a bit array
public static int bitsToInt(boolean[] bits) {
int n = 0, l = bits.length;
for (int i = 0; i < l; ++i) {
n = (n << 1) + (bits[i] ? 1 : 0);
}
return n;
}
Использование
// Generate Mnemonic Words, Mnemonic Phrase, and Seed
String[] mnemonicWords = generateMnemonic();
String mnemonicPhrase = "";
for (String word : mnemonicWords)
mnemonicPhrase += word;
byte[] seed = PBKDF2(mnemonicPhrase, "");
System.out.println("Seed: " + bytesToHex(seed));
Пример результатов
My Program Trial
Entropy (hex): 3CCB62D9AF76F1E8DB113E66B2D84656
Checksum bits: 1100
Raw Binary: 00111100110 01011011000 10110110011 01011110111 01101111000 11110100011 01101100010 00100111110 01100110101 10010110110 00010001100 1010110
Mnemonic: devote force reopen galaxy humor virtual hobby chief grit nothing bag pulse
Seed: 013FFA714C57AA26C59DC215880D9C2398A8B38D10D7E41A882CF98C35976F0BF26BCC08B0B196945DE8778C7FD561FB0F20A8B9BAD46B12196C963A85E3B40E
Expected Results (Derived from same Entropy)
Entropy (hex): 3CCB62D9AF76F1E8DB113E66B2D84656
Checksum bits: 1100
Raw Binary: 00111100110 01011011000 10110110011 01011110111 01101111000 11110100011 01101100010 00100111110 01100110101 10010110110 00010001100 1010110
Mnemonic: devote force reopen galaxy humor virtual hobby chief grit nothing bag pulse
Seed: 0c3c5f9ae724a2a3ed70aeb24919c10506e4962223a5375f70164be8b897d615ec9bf9f3e64a889cff03318cc5d0b3c8378ba0264d198e307c609632016ddd01