Семя из BIP39 Mnemonic не соответствует тестовым векторам - PullRequest
0 голосов
/ 11 апреля 2019

Я пишу 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

1 Ответ

1 голос
/ 12 апреля 2019

Похоже, я смог ответить на свой вопрос.В моей программе я объединял начальные слова без пробелов, используя

String mnemonicPhrase = "";
for (String word : mnemonicWords)
  mnemonicPhrase += word;

Но это не правильный формат, так как пробелы должны быть включены.Изменение этого блока кода для добавления пробелов:

String mnemonicPhrase = "";
for(int i=0; i<mnemonicWords.length; i++) {
    mnemonicPhrase += mnemonicWords[i];
    if(i < mnemonicWords.length-1) mnemonicPhrase += " ";
}

дает ожидаемые результаты тестового вектора, опубликованные здесь с использованием пароля "TREZOR".

...