来自 BIP39 助记符的种子与测试向量不匹配
Seed from BIP39 Mnemonic not matching Test Vectors
我正在编写 Java 比特币 BIP39 的实现。到目前为止,我的代码能够正确生成随机生成的助记词。但是,将 12 字助记词短语转换为 512 位种子时,生成的值与 Ian Coleman's BIP39 Tool.
的结果不匹配
首先,SecureRandom 对象生成一个随机的 512 位熵值 (ENT)。 ENT 值使用 SHA256 进行哈希计算以计算校验和值 (CS),即哈希的前 4 位。校验和连接到 ENT 的末尾以给出 ENT_CS。 ENT_CS被拆分成每段11位,以这11位对应的整数值作为索引号,从Word List中得到一个词。这会生成我的 12 字助记词。到目前为止,到目前为止的所有步骤都符合上述 BIP39 工具的预期结果。
为了创建种子,我将 PBKDF2 与 HmacSHA512 结合使用,将迭代次数设置为 2048,并将密钥大小设置为 512 位(64 字节)。我已经针对这些 Test Vectors, Google's "crypto" package implementation, and NovaCrypto's Java BIP39 implementation. The mnemonic words, excluding separators, is used as input along with a salt of "mnemonic"+password
as per the Bitcoin Core BIP39 Specifications.
测试了 PBKDF2 的实现
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
看来我能够回答我自己的问题了。
在我的程序中,我使用
连接没有空格的种子词
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".
的密码生成 here 发布的预期测试向量结果
我正在编写 Java 比特币 BIP39 的实现。到目前为止,我的代码能够正确生成随机生成的助记词。但是,将 12 字助记词短语转换为 512 位种子时,生成的值与 Ian Coleman's BIP39 Tool.
的结果不匹配首先,SecureRandom 对象生成一个随机的 512 位熵值 (ENT)。 ENT 值使用 SHA256 进行哈希计算以计算校验和值 (CS),即哈希的前 4 位。校验和连接到 ENT 的末尾以给出 ENT_CS。 ENT_CS被拆分成每段11位,以这11位对应的整数值作为索引号,从Word List中得到一个词。这会生成我的 12 字助记词。到目前为止,到目前为止的所有步骤都符合上述 BIP39 工具的预期结果。
为了创建种子,我将 PBKDF2 与 HmacSHA512 结合使用,将迭代次数设置为 2048,并将密钥大小设置为 512 位(64 字节)。我已经针对这些 Test Vectors, Google's "crypto" package implementation, and NovaCrypto's Java BIP39 implementation. The mnemonic words, excluding separators, is used as input along with a salt of "mnemonic"+password
as per the Bitcoin Core BIP39 Specifications.
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
看来我能够回答我自己的问题了。 在我的程序中,我使用
连接没有空格的种子词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".
的密码生成 here 发布的预期测试向量结果