check / check (push) Waiting to run
go-bip39's repository no longer exists. The part of v1.1.0 that secret uses (NewEntropy, NewMnemonic, NewSeed, IsMnemonicValid and what they call; English word list only) now lives in internal/bip39 with upstream's LICENSE beside it, along with upstream's tests of that code and their vectors unchanged. Every import moves there, and the module leaves go.mod and go.sum. Beyond the trimming, only what the linter asked for changed. No derived key or mnemonic changes. Model: opus-5-5
1192 lines
30 KiB
Go
1192 lines
30 KiB
Go
//nolint:gosec // G101: Test file contains BIP85 test vectors, not real credentials
|
|
package bip85_test
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/hex"
|
|
"errors"
|
|
"fmt"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/btcsuite/btcd/btcutil/hdkeychain"
|
|
"sneak.berlin/go/secret/internal/bip39"
|
|
"sneak.berlin/go/secret/pkg/bip85"
|
|
)
|
|
|
|
const (
|
|
// Test master BIP32 root key from the BIP85 specification
|
|
testMasterKey = "xprv9s21ZrQH143K2LBWUUQRFXhucrQqBpKdRRxNVq2zBqsx8HVqFk2uYo8" +
|
|
"kmbaLLHRdqtQpUm98uKfu3vca1LqdGhUtyoFnCNkfmXRyPXLjbKb"
|
|
|
|
// Test Case 1 - Basic entropy derivation with path m/83696968'/0'/0'
|
|
testCase1Path = "m/83696968'/0'/0'"
|
|
testCase1ExpectedDerivedKey = "cca20ccb0e9a90feb0912870c3323b24" +
|
|
"874b0ca3d8018c4b96d0b97c0e82ded0"
|
|
testCase1ExpectedEntropy = "efecfbccffea313214232d29e71563d941229afb4338c21f" +
|
|
"9517c41aaa0d16f00b83d2a09ef747e7a64e8e2bd5a14869" +
|
|
"e693da66ce94ac2da570ab7ee48618f7"
|
|
|
|
// Test Case 2 - Basic entropy derivation with path m/83696968'/0'/1'
|
|
testCase2Path = "m/83696968'/0'/1'"
|
|
testCase2ExpectedDerivedKey = "503776919131758bb7de7beb6c0ae248" +
|
|
"94f4ec042c26032890c29359216e21ba"
|
|
testCase2ExpectedEntropy = "70c6e3e8ebee8dc4c0dbba66076819bb8c09672527c4277c" +
|
|
"a8729532ad711872218f826919f6b67218adde99018a6df9" +
|
|
"095ab2b58d803b5b93ec9802085a690e"
|
|
|
|
// BIP85-DRNG-SHAKE256 test vector
|
|
drngTestPath = "m/83696968'/0'/0'"
|
|
drngExpected80Bytes = "b78b1ee6b345eae6836c2d53d33c64cdaf9a6964" +
|
|
"87be81b03e822dc84b3f1cd883d7559e53d175f2" +
|
|
"43e4c349e822a957bbff9224bc5dde9492ef54e8" +
|
|
"a439f6bc8c7355b87a925a37ee405a7502991111"
|
|
|
|
// Python DRNG test vectors
|
|
pythonDRNG50BytesExpected = "b78b1ee6b345eae6836c2d53d33c64cdaf9a6964" +
|
|
"87be81b03e822dc84b3f1cd883d7559e53d175f2" +
|
|
"43e4c349e822a957bbff"
|
|
pythonDRNG100BytesExpected = "9224bc5dde9492ef54e8a439f6bc8c7355b87a92" +
|
|
"5a37ee405a7502991111cd2dddaf1883f4e962ab" +
|
|
"f4fb4b31cd28d5cf6b14f6ddcc9c19fd56d7f960" +
|
|
"a4b27f1d423a55dda4865aa6ddd6b4c26f18d400" +
|
|
"bb0a593e6c785d6d7e28c9c64608624318eddc01"
|
|
pythonDRNG150BytesExpected = "23750caa2a271f35faa6a3ca292b4be357404eca" +
|
|
"6842c69a3717dc3e41f7b38c67be492395b32221" +
|
|
"470aa08a2c489018c635a175f731245330e1f470" +
|
|
"91dbfb26f2923d10bd2e09280bffd1d94eb2a88f" +
|
|
"964aeb1774da04aad3bb1fdde0f77cd5ca79617a" +
|
|
"e317375417a51339523057bebef434c440030389" +
|
|
"0332e458425242f56a4293dad4f632b82713467b" +
|
|
"18ed6e1dab633220523d"
|
|
pythonDRNG20BytesExpected = "b78b1ee6b345eae6836c2d53d33c64cdaf9a6964"
|
|
pythonDRNG25BytesExpected = "87be81b03e822dc84b3f1cd883d7559e53d175f243e4c349e8"
|
|
|
|
// BIP39 12 English words test vector
|
|
bip39_12WordsPath = "m/83696968'/39'/0'/12'/0'"
|
|
bip39_12WordsExpectedEntropy = "6250b68daf746d12a24d58b4787a714b"
|
|
bip39_12WordsExpectedMnemonic = "girl mad pet galaxy egg matter matrix prison " +
|
|
"refuse sense ordinary nose"
|
|
|
|
// BIP39 18 English words test vector
|
|
bip39_18WordsPath = "m/83696968'/39'/0'/18'/0'"
|
|
bip39_18WordsExpectedEntropy = "938033ed8b12698449d4bbca3c853c66b293ea1b1ce9d9dc"
|
|
bip39_18WordsExpectedMnemonic = "near account window bike charge season chef " +
|
|
"number sketch tomorrow excuse sniff circle vital hockey " +
|
|
"outdoor supply token"
|
|
|
|
// BIP39 24 English words test vector
|
|
bip39_24WordsPath = "m/83696968'/39'/0'/24'/0'"
|
|
bip39_24WordsExpectedEntropy = "ae131e2312cdc61331542efe0d1077ba" +
|
|
"c5ea803adf24b313a4f0e48e9c51f37f"
|
|
bip39_24WordsExpectedMnemonic = "puppy ocean match cereal symbol another " +
|
|
"shed magic wrap hammer bulb intact gadget divorce twin tonight " +
|
|
"reason outdoor destroy simple truth cigar social volcano"
|
|
|
|
// HD-Seed WIF test vector
|
|
hdWifPath = "m/83696968'/2'/0'"
|
|
hdWifExpectedEntropy = "7040bb53104f27367f317558e78a994a" +
|
|
"da7296c6fde36a364e5baf206e502bb1"
|
|
hdWifExpectedWIF = "Kzyv4uF39d4Jrw2W7UryTHwZr1zQVNk4dAFyqE6BuMrMh1Za7uhp"
|
|
|
|
// XPRV test vector
|
|
xprvPath = "m/83696968'/32'/0'"
|
|
xprvExpectedKey = "xprv9s21ZrQH143K2srSbCSg4m4kLvPMzcWydgmKEnMmoZUurYuBuYG46c6" +
|
|
"P71UGXMzmriLzCCBvKQWBUv3vPB3m1SATMhp3uEjXHJ42jFg7myX"
|
|
|
|
// HEX test vector
|
|
hexPath = "m/83696968'/128169'/64'/0'"
|
|
hexExpectedEntropy = "492db4698cf3b73a5a24998aa3e9d7fa96275d85724a91e7" +
|
|
"1aa2d645442f878555d078fd1f1f67e368976f04137b1f7a" +
|
|
"0d19232136ca50c44614af72b5582a5c"
|
|
|
|
// PWD Base64 test vector
|
|
pwdBase64Path = "m/83696968'/707764'/21'/0'"
|
|
pwdBase64ExpectedEntropy = "74a2e87a9ba0cdd549bdd2f9ea880d554c6c355b08ed2508" +
|
|
"8cfa88f3f1c4f74632b652fd4a8f5fda43074c6f6964a375" +
|
|
"3b08bb5210c8f5e75c07a4c2a20bf6e9"
|
|
pwdBase64ExpectedPassword = "dKLoepugzdVJvdL56ogNV"
|
|
|
|
// PWD Base85 test vector
|
|
pwdBase85Path = "m/83696968'/707785'/12'/0'"
|
|
pwdBase85ExpectedEntropy = "f7cfe56f63dca2490f65fcbf9ee63dcd85d18f751b6b5e1c" +
|
|
"1b8733af6459c904a75e82b4a22efff9b9e69de2144b293a" +
|
|
"a8714319a054b6cb55826a8e51425209"
|
|
pwdBase85ExpectedPassword = "_s`{TW89)i4`"
|
|
|
|
// Test keys for parsing tests
|
|
testInvalidMasterKey = "xprv9s21ZrQH143K2LBWUUQRFXhucrQqBpKdRRxNVq2zBqsx8HVqFk2uYo8" +
|
|
"kmbaLLHRdqtQpUm98uKfu3vca1LqdGhUtyoFnCNkfmXRyPXLjbXX"
|
|
testTestnetMasterKey = "tprv8ZgxMBicQKsPeWHBt7a68nPnvgTnuDhUgDWC8wZCgA8GahrQ3f3uWpq7" +
|
|
"wE7Uc1dLBnCe1hhCZ886K6ND37memRDWqsA9HgSKDXtwh2Qxo6J"
|
|
)
|
|
|
|
// logTestVector logs test information in a cleaner, more concise format
|
|
func logTestVector(t *testing.T, title string) {
|
|
t.Helper()
|
|
t.Logf("=== TEST: %s ===", title)
|
|
}
|
|
|
|
// mustParseTestMasterKey parses the shared test master key.
|
|
func mustParseTestMasterKey(t *testing.T) *hdkeychain.ExtendedKey {
|
|
t.Helper()
|
|
|
|
masterKey, err := bip85.ParseMasterKey(testMasterKey)
|
|
if err != nil {
|
|
t.Fatalf("Failed to parse master key: %v", err)
|
|
}
|
|
|
|
return masterKey
|
|
}
|
|
|
|
// checkDerivedChildKey derives the child key at path and compares it
|
|
// against the expected hex value.
|
|
func checkDerivedChildKey(t *testing.T, path, expected string) {
|
|
t.Helper()
|
|
|
|
masterKey := mustParseTestMasterKey(t)
|
|
|
|
t.Logf("Deriving key for path: %s", path)
|
|
|
|
derivedKeyBytes, err := bip85.DeriveChildKey(masterKey, path)
|
|
if err != nil {
|
|
t.Fatalf("Failed to derive child key: %v", err)
|
|
}
|
|
|
|
derivedKeyHex := hex.EncodeToString(derivedKeyBytes)
|
|
|
|
t.Logf("EXPECTED: %s", expected)
|
|
t.Logf("ACTUAL: %s", derivedKeyHex)
|
|
|
|
if derivedKeyHex != expected {
|
|
t.Errorf(
|
|
"Expected derived key bytes %s, got %s",
|
|
expected,
|
|
derivedKeyHex,
|
|
)
|
|
} else {
|
|
t.Logf("RESULT: PASS")
|
|
}
|
|
}
|
|
|
|
// TestDerivedKey tests the derived key directly
|
|
func TestDerivedKey(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
logTestVector(t, "Derived Child Keys")
|
|
|
|
// Test case 1
|
|
checkDerivedChildKey(t, testCase1Path, testCase1ExpectedDerivedKey)
|
|
|
|
// Test case 2
|
|
checkDerivedChildKey(t, testCase2Path, testCase2ExpectedDerivedKey)
|
|
}
|
|
|
|
// runEntropyVectorTest checks a BIP85 entropy derivation test vector.
|
|
func runEntropyVectorTest(t *testing.T, title, path, expectedEntropy string) {
|
|
t.Helper()
|
|
|
|
logTestVector(t, title)
|
|
|
|
masterKey := mustParseTestMasterKey(t)
|
|
|
|
t.Logf("Test path: %s", path)
|
|
|
|
entropy, err := bip85.DeriveBIP85Entropy(masterKey, path)
|
|
if err != nil {
|
|
t.Fatalf("Failed to derive entropy: %v", err)
|
|
}
|
|
|
|
derivedEntropyHex := hex.EncodeToString(entropy)
|
|
|
|
t.Logf("EXPECTED: %s", expectedEntropy)
|
|
t.Logf("ACTUAL: %s", derivedEntropyHex)
|
|
|
|
if derivedEntropyHex != expectedEntropy {
|
|
t.Errorf(
|
|
"Expected derived entropy %s, got %s",
|
|
expectedEntropy,
|
|
derivedEntropyHex,
|
|
)
|
|
} else {
|
|
t.Logf("RESULT: PASS")
|
|
}
|
|
}
|
|
|
|
// TestCase1 tests the first test vector from the BIP85 specification
|
|
func TestCase1(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
runEntropyVectorTest(t, "Test Case 1", testCase1Path, testCase1ExpectedEntropy)
|
|
}
|
|
|
|
// TestCase2 tests the second test vector from the BIP85 specification
|
|
func TestCase2(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
runEntropyVectorTest(t, "Test Case 2", testCase2Path, testCase2ExpectedEntropy)
|
|
}
|
|
|
|
// runBIP39VectorTest checks a BIP39 mnemonic derivation test vector.
|
|
func runBIP39VectorTest(
|
|
t *testing.T,
|
|
title, path string,
|
|
words uint32,
|
|
expectedEntropy, expectedMnemonic string,
|
|
) {
|
|
t.Helper()
|
|
|
|
logTestVector(t, title)
|
|
|
|
masterKey := mustParseTestMasterKey(t)
|
|
|
|
t.Logf("Path: %s", path)
|
|
t.Logf("Parameters: Language=English(0), Words=%d, Index=0", words)
|
|
|
|
// Derive the BIP39 mnemonic entropy
|
|
entropy, err := bip85.DeriveBIP39Entropy(masterKey, 0, words, 0)
|
|
if err != nil {
|
|
t.Fatalf("Failed to derive BIP39 entropy: %v", err)
|
|
}
|
|
|
|
derivedEntropyHex := hex.EncodeToString(entropy)
|
|
|
|
t.Logf("EXPECTED ENTROPY: %s", expectedEntropy)
|
|
t.Logf("ACTUAL ENTROPY: %s", derivedEntropyHex)
|
|
|
|
if derivedEntropyHex != expectedEntropy {
|
|
t.Errorf(
|
|
"Expected derived entropy %s, got %s",
|
|
expectedEntropy,
|
|
derivedEntropyHex,
|
|
)
|
|
} else {
|
|
t.Logf("ENTROPY MATCH: PASS")
|
|
}
|
|
|
|
// Convert entropy to mnemonic
|
|
mnemonic, err := bip39.NewMnemonic(entropy)
|
|
if err != nil {
|
|
t.Fatalf("Failed to create mnemonic: %v", err)
|
|
}
|
|
|
|
t.Logf("EXPECTED MNEMONIC: %s", expectedMnemonic)
|
|
t.Logf("ACTUAL MNEMONIC: %s", mnemonic)
|
|
|
|
if mnemonic != expectedMnemonic {
|
|
t.Errorf(
|
|
"Expected mnemonic '%s', got '%s'",
|
|
expectedMnemonic,
|
|
mnemonic,
|
|
)
|
|
} else {
|
|
t.Logf("MNEMONIC MATCH: PASS")
|
|
}
|
|
}
|
|
|
|
// TestBIP39_12EnglishWords tests the BIP39 12 English words test vector
|
|
func TestBIP39_12EnglishWords(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
runBIP39VectorTest(
|
|
t,
|
|
"BIP39 12 English Words",
|
|
bip39_12WordsPath,
|
|
12,
|
|
bip39_12WordsExpectedEntropy,
|
|
bip39_12WordsExpectedMnemonic,
|
|
)
|
|
}
|
|
|
|
// TestBIP39_18EnglishWords tests the BIP39 18 English words test vector
|
|
func TestBIP39_18EnglishWords(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
runBIP39VectorTest(
|
|
t,
|
|
"BIP39 18 English Words",
|
|
bip39_18WordsPath,
|
|
18,
|
|
bip39_18WordsExpectedEntropy,
|
|
bip39_18WordsExpectedMnemonic,
|
|
)
|
|
}
|
|
|
|
// TestBIP39_24EnglishWords tests the BIP39 24 English words test vector
|
|
func TestBIP39_24EnglishWords(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
runBIP39VectorTest(
|
|
t,
|
|
"BIP39 24 English Words",
|
|
bip39_24WordsPath,
|
|
24,
|
|
bip39_24WordsExpectedEntropy,
|
|
bip39_24WordsExpectedMnemonic,
|
|
)
|
|
}
|
|
|
|
// TestHD_WIF tests the WIF test vector
|
|
func TestHD_WIF(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
logTestVector(t, "HD-Seed WIF")
|
|
|
|
masterKey := mustParseTestMasterKey(t)
|
|
|
|
// First verify the entropy derivation
|
|
t.Logf("Path: %s", hdWifPath)
|
|
|
|
entropy, err := bip85.DeriveBIP85Entropy(masterKey, hdWifPath)
|
|
if err != nil {
|
|
t.Fatalf("Failed to derive entropy: %v", err)
|
|
}
|
|
|
|
// Expected entropy from BIP85 spec; WIF uses first 32 bytes
|
|
derivedEntropyHex := hex.EncodeToString(entropy[:32])
|
|
|
|
if derivedEntropyHex != hdWifExpectedEntropy {
|
|
t.Errorf(
|
|
"Entropy mismatch!\nExpected: %s\nGot: %s",
|
|
hdWifExpectedEntropy,
|
|
derivedEntropyHex,
|
|
)
|
|
}
|
|
|
|
// Now test the WIF derivation
|
|
wif, err := bip85.DeriveWIFKey(masterKey, 0)
|
|
if err != nil {
|
|
t.Fatalf("Failed to derive WIF key: %v", err)
|
|
}
|
|
|
|
t.Logf("EXPECTED WIF: %s", hdWifExpectedWIF)
|
|
t.Logf("ACTUAL WIF: %s", wif)
|
|
|
|
if wif != hdWifExpectedWIF {
|
|
t.Errorf("Expected WIF %s, got %s", hdWifExpectedWIF, wif)
|
|
} else {
|
|
t.Logf("RESULT: PASS")
|
|
}
|
|
}
|
|
|
|
// TestXPRV tests the XPRV test vector
|
|
func TestXPRV(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
logTestVector(t, "XPRV")
|
|
|
|
masterKey := mustParseTestMasterKey(t)
|
|
|
|
t.Logf("Path: %s", xprvPath)
|
|
|
|
derivedKey, err := bip85.DeriveXPRV(masterKey, 0)
|
|
if err != nil {
|
|
t.Fatalf("Failed to derive XPRV: %v", err)
|
|
}
|
|
|
|
derivedXPRV := derivedKey.String()
|
|
|
|
t.Logf("EXPECTED XPRV: %s", xprvExpectedKey)
|
|
t.Logf("ACTUAL XPRV: %s", derivedXPRV)
|
|
|
|
if derivedXPRV != xprvExpectedKey {
|
|
t.Errorf("Expected XPRV %s, got %s", xprvExpectedKey, derivedXPRV)
|
|
} else {
|
|
t.Logf("RESULT: PASS")
|
|
}
|
|
}
|
|
|
|
// TestDRNG_SHAKE256 tests the BIP85-DRNG-SHAKE256 test vector
|
|
func TestDRNG_SHAKE256(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
logTestVector(t, "DRNG-SHAKE256")
|
|
|
|
masterKey := mustParseTestMasterKey(t)
|
|
|
|
// Derive entropy for the DRNG
|
|
entropy, err := bip85.DeriveBIP85Entropy(masterKey, drngTestPath)
|
|
if err != nil {
|
|
t.Fatalf("Failed to derive entropy: %v", err)
|
|
}
|
|
|
|
// Create DRNG
|
|
drng := bip85.NewBIP85DRNG(entropy)
|
|
|
|
// Read 80 bytes
|
|
buffer := make([]byte, 80)
|
|
|
|
n, err := drng.Read(buffer)
|
|
if err != nil {
|
|
t.Fatalf("Failed to read from DRNG: %v", err)
|
|
}
|
|
|
|
if n != 80 {
|
|
t.Errorf("Expected to read 80 bytes, got %d", n)
|
|
}
|
|
|
|
hexOutput := hex.EncodeToString(buffer)
|
|
|
|
if !strings.EqualFold(hexOutput, drngExpected80Bytes) {
|
|
t.Errorf(
|
|
"Expected DRNG output:\n%s\n\nGot:\n%s",
|
|
drngExpected80Bytes,
|
|
hexOutput,
|
|
)
|
|
}
|
|
}
|
|
|
|
// readDRNGHex reads size bytes from drng and returns the hex encoding.
|
|
func readDRNGHex(t *testing.T, drng *bip85.DRNG, size int, label string) string {
|
|
t.Helper()
|
|
|
|
buf := make([]byte, size)
|
|
|
|
_, err := drng.Read(buf)
|
|
if err != nil {
|
|
t.Fatalf("Failed to read %s from DRNG: %v", label, err)
|
|
}
|
|
|
|
return hex.EncodeToString(buf)
|
|
}
|
|
|
|
// TestPythonDRNGVectors tests the DRNG vectors from the Python implementation
|
|
func TestPythonDRNGVectors(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
logTestVector(t, "Python DRNG Vectors")
|
|
|
|
masterKey := mustParseTestMasterKey(t)
|
|
|
|
// Derive entropy for the DRNG
|
|
entropy, err := bip85.DeriveBIP85Entropy(masterKey, drngTestPath)
|
|
if err != nil {
|
|
t.Fatalf("Failed to derive entropy: %v", err)
|
|
}
|
|
|
|
// Create DRNG
|
|
drng := bip85.NewBIP85DRNG(entropy)
|
|
|
|
// Test vector 1: Read 50 bytes
|
|
actual1 := readDRNGHex(t, drng, 50, "50 bytes")
|
|
if actual1 != pythonDRNG50BytesExpected {
|
|
t.Errorf(
|
|
"Test vector 1 failed. Expected:\n%s\n\nGot:\n%s",
|
|
pythonDRNG50BytesExpected,
|
|
actual1,
|
|
)
|
|
}
|
|
|
|
// Test vector 2: Read 100 bytes
|
|
actual2 := readDRNGHex(t, drng, 100, "100 bytes")
|
|
if actual2 != pythonDRNG100BytesExpected {
|
|
t.Errorf(
|
|
"Test vector 2 failed. Expected:\n%s\n\nGot:\n%s",
|
|
pythonDRNG100BytesExpected,
|
|
actual2,
|
|
)
|
|
}
|
|
|
|
// Test vector 3: Read 150 bytes
|
|
actual3 := readDRNGHex(t, drng, 150, "150 bytes")
|
|
if actual3 != pythonDRNG150BytesExpected {
|
|
t.Errorf(
|
|
"Test vector 3 failed. Expected:\n%s\n\nGot:\n%s",
|
|
pythonDRNG150BytesExpected,
|
|
actual3,
|
|
)
|
|
}
|
|
|
|
// Test with fresh DRNG
|
|
drng2 := bip85.NewBIP85DRNG(entropy)
|
|
|
|
actual4 := readDRNGHex(t, drng2, 20, "20 bytes")
|
|
if actual4 != pythonDRNG20BytesExpected {
|
|
t.Errorf(
|
|
"Test vector 4 failed. Expected:\n%s\n\nGot:\n%s",
|
|
pythonDRNG20BytesExpected,
|
|
actual4,
|
|
)
|
|
}
|
|
|
|
// Read another 25 bytes
|
|
actual5 := readDRNGHex(t, drng2, 25, "25 bytes")
|
|
if actual5 != pythonDRNG25BytesExpected {
|
|
t.Errorf(
|
|
"Test vector 5 failed. Expected:\n%s\n\nGot:\n%s",
|
|
pythonDRNG25BytesExpected,
|
|
actual5,
|
|
)
|
|
}
|
|
}
|
|
|
|
// drngReadChunks performs sequential reads of the given sizes from drng
|
|
// and returns the concatenated output.
|
|
func drngReadChunks(t *testing.T, drng *bip85.DRNG, name string, sizes ...int) []byte {
|
|
t.Helper()
|
|
|
|
total := 0
|
|
|
|
for _, size := range sizes {
|
|
total += size
|
|
}
|
|
|
|
out := make([]byte, 0, total)
|
|
|
|
for _, size := range sizes {
|
|
buf := make([]byte, size)
|
|
|
|
_, err := drng.Read(buf)
|
|
if err != nil {
|
|
t.Fatalf("Failed to read from %s: %v", name, err)
|
|
}
|
|
|
|
out = append(out, buf...)
|
|
}
|
|
|
|
return out
|
|
}
|
|
|
|
// TestDRNGDeterminism tests the deterministic behavior of the DRNG
|
|
func TestDRNGDeterminism(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
logTestVector(t, "DRNG Determinism")
|
|
|
|
masterKey := mustParseTestMasterKey(t)
|
|
|
|
// Derive entropy for the DRNG
|
|
entropy, err := bip85.DeriveBIP85Entropy(masterKey, drngTestPath)
|
|
if err != nil {
|
|
t.Fatalf("Failed to derive entropy: %v", err)
|
|
}
|
|
|
|
// Create 3 DRNGs with the same seed
|
|
drng1 := bip85.NewBIP85DRNG(entropy)
|
|
drng2 := bip85.NewBIP85DRNG(entropy)
|
|
drng3 := bip85.NewBIP85DRNG(entropy)
|
|
|
|
// Read the same amount of data with differently sized read calls
|
|
result1 := drngReadChunks(t, drng1, "drng1", 10, 20, 30, 40)
|
|
result2 := drngReadChunks(t, drng2, "drng2", 40, 30, 20, 10)
|
|
buf3 := drngReadChunks(t, drng3, "drng3", 100)
|
|
|
|
// All results should be identical
|
|
if !bytes.Equal(result1, result2) {
|
|
t.Errorf("Expected drng1 and drng2 to produce identical outputs")
|
|
}
|
|
|
|
if !bytes.Equal(result2, buf3) {
|
|
t.Errorf("Expected drng2 and drng3 to produce identical outputs")
|
|
}
|
|
}
|
|
|
|
// TestDRNGLengths tests the DRNG with different lengths
|
|
func TestDRNGLengths(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
logTestVector(t, "DRNG Lengths")
|
|
|
|
masterKey := mustParseTestMasterKey(t)
|
|
|
|
// Derive entropy for the DRNG
|
|
entropy, err := bip85.DeriveBIP85Entropy(masterKey, drngTestPath)
|
|
if err != nil {
|
|
t.Fatalf("Failed to derive entropy: %v", err)
|
|
}
|
|
|
|
// Create DRNG
|
|
drng := bip85.NewBIP85DRNG(entropy)
|
|
|
|
// Test various lengths
|
|
lengths := []int{1, 10, 100, 1000, 10000}
|
|
for _, length := range lengths {
|
|
buffer := make([]byte, length)
|
|
|
|
n, err := drng.Read(buffer)
|
|
if err != nil {
|
|
t.Errorf("Failed to read %d bytes: %v", length, err)
|
|
|
|
continue
|
|
}
|
|
|
|
if n != length {
|
|
t.Errorf("Expected to read %d bytes, got %d", length, n)
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestDRNGExceptions tests error handling in the DRNG
|
|
func TestDRNGExceptions(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
logTestVector(t, "DRNG Exceptions")
|
|
|
|
// Test with entropy of the wrong size
|
|
testCases := []int{0, 1, 32, 63, 65, 128}
|
|
|
|
for _, size := range testCases {
|
|
t.Run(fmt.Sprintf("EntropySize_%d", size), func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
entropy := make([]byte, size)
|
|
|
|
// Use a function to capture the panic
|
|
testPanic := func() {
|
|
defer func() {
|
|
if r := recover(); r != nil {
|
|
// Expected behavior - panic occurred
|
|
return
|
|
}
|
|
}()
|
|
|
|
// This should panic for any size != 64
|
|
_ = bip85.NewBIP85DRNG(entropy)
|
|
|
|
// If we get here without panic, it's an error
|
|
t.Errorf(
|
|
"Expected panic for entropy length %d, but it didn't happen",
|
|
size,
|
|
)
|
|
}
|
|
|
|
testPanic()
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestDRNGDifferentSizes tests the DRNG with different buffer sizes
|
|
func TestDRNGDifferentSizes(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
logTestVector(t, "DRNG Different Sizes")
|
|
|
|
masterKey := mustParseTestMasterKey(t)
|
|
|
|
entropy, err := bip85.DeriveBIP85Entropy(masterKey, drngTestPath)
|
|
if err != nil {
|
|
t.Fatalf("Failed to derive entropy: %v", err)
|
|
}
|
|
|
|
// Create DRNG
|
|
drng := bip85.NewBIP85DRNG(entropy)
|
|
|
|
// Test reading different sizes
|
|
for _, size := range []int{32, 64, 128, 256} {
|
|
buffer := make([]byte, size)
|
|
|
|
n, err := drng.Read(buffer)
|
|
if err != nil {
|
|
t.Fatalf("Failed to read %d bytes from DRNG: %v", size, err)
|
|
}
|
|
|
|
if n != size {
|
|
t.Errorf("Expected to read %d bytes, got %d", size, n)
|
|
}
|
|
}
|
|
|
|
// Test deterministic behavior - two DRNGs with the same seed should
|
|
// produce the same output
|
|
drng1 := bip85.NewBIP85DRNG(entropy)
|
|
drng2 := bip85.NewBIP85DRNG(entropy)
|
|
|
|
buffer1 := make([]byte, 32)
|
|
buffer2 := make([]byte, 32)
|
|
|
|
_, err = drng1.Read(buffer1)
|
|
if err != nil {
|
|
t.Fatalf("Failed to read from first DRNG: %v", err)
|
|
}
|
|
|
|
_, err = drng2.Read(buffer2)
|
|
if err != nil {
|
|
t.Fatalf("Failed to read from second DRNG: %v", err)
|
|
}
|
|
|
|
if !bytes.Equal(buffer1, buffer2) {
|
|
t.Errorf("Expected identical outputs from DRNGs with same seed")
|
|
}
|
|
|
|
// Reading another 32 bytes should produce different output from the
|
|
// first read
|
|
buffer3 := make([]byte, 32)
|
|
|
|
_, err = drng1.Read(buffer3)
|
|
if err != nil {
|
|
t.Fatalf("Failed to read second buffer from DRNG: %v", err)
|
|
}
|
|
|
|
if bytes.Equal(buffer1, buffer3) {
|
|
t.Errorf("Expected different outputs from sequential reads")
|
|
}
|
|
}
|
|
|
|
// TestMasterKeyParsing tests parsing of different master key formats
|
|
func TestMasterKeyParsing(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
logTestVector(t, "Master Key Parsing")
|
|
|
|
// Test valid master key
|
|
t.Logf("Testing valid master key")
|
|
|
|
_, err := bip85.ParseMasterKey(testMasterKey)
|
|
if err != nil {
|
|
t.Errorf("Failed to parse valid master key: %v", err)
|
|
} else {
|
|
t.Logf("Valid master key parsed successfully: PASS")
|
|
}
|
|
|
|
// Test invalid master key (wrong checksum)
|
|
t.Logf("Testing invalid master key (corrupted)")
|
|
|
|
_, err = bip85.ParseMasterKey(testInvalidMasterKey)
|
|
if err == nil {
|
|
t.Errorf("Expected error for invalid master key, but got nil")
|
|
} else {
|
|
t.Logf("Got expected error for invalid master key: %v", err)
|
|
t.Logf("RESULT: PASS")
|
|
}
|
|
|
|
// Test testnet master key (tprv)
|
|
t.Logf("Testing testnet master key format")
|
|
|
|
testnetMasterKey, err := bip85.ParseMasterKey(testTestnetMasterKey)
|
|
if err != nil {
|
|
t.Errorf("Failed to parse testnet master key: %v", err)
|
|
|
|
return
|
|
}
|
|
|
|
t.Logf("Testnet master key parsed successfully: PASS")
|
|
|
|
// Test that XPRV derivation using a testnet master key produces a
|
|
// testnet XPRV
|
|
derivedKey, err := bip85.DeriveXPRV(testnetMasterKey, 0)
|
|
if err != nil {
|
|
t.Fatalf("Failed to derive XPRV from testnet key: %v", err)
|
|
}
|
|
|
|
derivedKeyStr := derivedKey.String()
|
|
if !strings.HasPrefix(derivedKeyStr, "tprv") {
|
|
t.Errorf(
|
|
"Expected derived key to be testnet (tprv prefix), got: %s",
|
|
derivedKeyStr,
|
|
)
|
|
} else {
|
|
t.Logf("Testnet XPRV derived successfully: %s", derivedKeyStr)
|
|
t.Logf("RESULT: PASS")
|
|
}
|
|
}
|
|
|
|
// TestDifferentPathFormats tests different path format expressions
|
|
func TestDifferentPathFormats(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
logTestVector(t, "Path Formats")
|
|
|
|
masterKey := mustParseTestMasterKey(t)
|
|
|
|
// Define equivalent paths in different formats
|
|
paths := []string{
|
|
"m/83696968'/0'/0'",
|
|
"m/83696968h/0h/0h",
|
|
"/83696968'/0'/0'",
|
|
"83696968'/0'/0'",
|
|
}
|
|
|
|
results := make([][]byte, 0, len(paths))
|
|
|
|
// Derive entropy using each path
|
|
for i, path := range paths {
|
|
t.Logf("Testing path format %d: %s", i+1, path)
|
|
|
|
entropy, err := bip85.DeriveBIP85Entropy(masterKey, path)
|
|
if err != nil {
|
|
t.Errorf("Failed to derive entropy with path %s: %v", path, err)
|
|
|
|
continue
|
|
}
|
|
|
|
results = append(results, entropy)
|
|
|
|
t.Logf("Derivation succeeded: PASS")
|
|
}
|
|
|
|
// Verify all results are the same
|
|
for i := 1; i < len(results); i++ {
|
|
if !bytes.Equal(results[0], results[i]) {
|
|
t.Errorf(
|
|
"Path %s produced different entropy than path %s",
|
|
paths[0],
|
|
paths[i],
|
|
)
|
|
}
|
|
}
|
|
|
|
if len(results) > 1 {
|
|
t.Logf("All equivalent path formats produced the same entropy: PASS")
|
|
}
|
|
}
|
|
|
|
// TestDirectBase85Encoding tests direct Base85 encoding with the test vector entropy
|
|
func TestDirectBase85Encoding(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
logTestVector(t, "Direct Base85 Encoding")
|
|
|
|
// Parse the master key
|
|
masterKey := mustParseTestMasterKey(t)
|
|
|
|
// First, derive the entropy and verify it matches the test vector
|
|
derivedEntropy, err := bip85.DeriveBIP85Entropy(masterKey, pwdBase85Path)
|
|
if err != nil {
|
|
t.Fatalf("Failed to derive entropy: %v", err)
|
|
}
|
|
|
|
// This is the expected entropy from the BIP85 spec for the Base85
|
|
// test vector
|
|
expectedEntropy, err := hex.DecodeString(pwdBase85ExpectedEntropy)
|
|
if err != nil {
|
|
t.Fatalf("Failed to decode expected entropy hex: %v", err)
|
|
}
|
|
|
|
// Verify the derived entropy matches the expected entropy
|
|
derivedEntropyHex := hex.EncodeToString(derivedEntropy)
|
|
if derivedEntropyHex != pwdBase85ExpectedEntropy {
|
|
t.Errorf(
|
|
"Entropy mismatch!\nExpected: %s\nGot: %s",
|
|
pwdBase85ExpectedEntropy,
|
|
derivedEntropyHex,
|
|
)
|
|
}
|
|
|
|
// Verify the entropy bytes match
|
|
if !bytes.Equal(derivedEntropy, expectedEntropy) {
|
|
t.Errorf("Entropy bytes do not match the test vector")
|
|
}
|
|
|
|
// Now test the password generation
|
|
pwd, err := bip85.DeriveBase85Password(masterKey, 12, 0)
|
|
if err != nil {
|
|
t.Fatalf("Failed to derive Base85 password: %v", err)
|
|
}
|
|
|
|
// Expected password from the test vector
|
|
if pwd != pwdBase85ExpectedPassword {
|
|
t.Errorf(
|
|
"Password mismatch!\nExpected: '%s'\nGot: '%s'",
|
|
pwdBase85ExpectedPassword,
|
|
pwd,
|
|
)
|
|
}
|
|
}
|
|
|
|
// runPasswordVectorTest checks a BIP85 password derivation test vector.
|
|
func runPasswordVectorTest(
|
|
t *testing.T,
|
|
title, path string,
|
|
pwdLen uint32,
|
|
derive func(*hdkeychain.ExtendedKey, uint32, uint32) (string, error),
|
|
expectedEntropy, expectedPassword string,
|
|
) {
|
|
t.Helper()
|
|
|
|
logTestVector(t, title)
|
|
|
|
masterKey := mustParseTestMasterKey(t)
|
|
|
|
// Testing with the example from the BIP85 spec
|
|
t.Logf("Path: %s", path)
|
|
t.Logf("Parameters: Length=%d, Index=0", pwdLen)
|
|
|
|
// First verify the entropy derivation
|
|
entropy, err := bip85.DeriveBIP85Entropy(masterKey, path)
|
|
if err != nil {
|
|
t.Fatalf("Failed to derive entropy: %v", err)
|
|
}
|
|
|
|
// Expected entropy from BIP85 spec
|
|
derivedEntropyHex := hex.EncodeToString(entropy)
|
|
|
|
if derivedEntropyHex != expectedEntropy {
|
|
t.Errorf(
|
|
"Entropy mismatch!\nExpected: %s\nGot: %s",
|
|
expectedEntropy,
|
|
derivedEntropyHex,
|
|
)
|
|
}
|
|
|
|
// Now test the password generation
|
|
pwd, err := derive(masterKey, pwdLen, 0)
|
|
if err != nil {
|
|
t.Fatalf("Failed to derive password: %v", err)
|
|
}
|
|
|
|
// The test vector from the BIP85 specification
|
|
t.Logf("EXPECTED PASSWORD: %s", expectedPassword)
|
|
t.Logf("ACTUAL PASSWORD: %s", pwd)
|
|
|
|
if pwd != expectedPassword {
|
|
t.Errorf("Expected password '%s', got '%s'", expectedPassword, pwd)
|
|
} else {
|
|
t.Logf("RESULT: PASS")
|
|
}
|
|
|
|
t.Logf("Password length: %d characters", len(pwd))
|
|
}
|
|
|
|
// TestPWDBase64 tests the Base64 password test vector
|
|
func TestPWDBase64(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
runPasswordVectorTest(
|
|
t,
|
|
"PWD Base64",
|
|
pwdBase64Path,
|
|
21,
|
|
bip85.DeriveBase64Password,
|
|
pwdBase64ExpectedEntropy,
|
|
pwdBase64ExpectedPassword,
|
|
)
|
|
}
|
|
|
|
// TestPWDBase85 tests the Base85 password test vector
|
|
func TestPWDBase85(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
runPasswordVectorTest(
|
|
t,
|
|
"PWD Base85",
|
|
pwdBase85Path,
|
|
12,
|
|
bip85.DeriveBase85Password,
|
|
pwdBase85ExpectedEntropy,
|
|
pwdBase85ExpectedPassword,
|
|
)
|
|
}
|
|
|
|
// TestHexDerivation tests the HEX derivation test vector
|
|
func TestHexDerivation(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
logTestVector(t, "HEX Derivation")
|
|
|
|
masterKey := mustParseTestMasterKey(t)
|
|
|
|
// Test vector from BIP85 spec
|
|
t.Logf("Path: %s", hexPath)
|
|
t.Logf("Parameters: NumBytes=64, Index=0")
|
|
|
|
// First verify the entropy derivation
|
|
entropy, err := bip85.DeriveBIP85Entropy(masterKey, hexPath)
|
|
if err != nil {
|
|
t.Fatalf("Failed to derive entropy: %v", err)
|
|
}
|
|
|
|
// Expected entropy from BIP85 spec; HEX uses first 64 bytes
|
|
derivedEntropyHex := hex.EncodeToString(entropy[:64])
|
|
|
|
if derivedEntropyHex != hexExpectedEntropy {
|
|
t.Errorf(
|
|
"Entropy mismatch!\nExpected: %s\nGot: %s",
|
|
hexExpectedEntropy,
|
|
derivedEntropyHex,
|
|
)
|
|
}
|
|
|
|
// Now test the hex derivation
|
|
hexData, err := bip85.DeriveHex(masterKey, 64, 0)
|
|
if err != nil {
|
|
t.Fatalf("Failed to derive hex data: %v", err)
|
|
}
|
|
|
|
if hexData != hexExpectedEntropy {
|
|
t.Errorf(
|
|
"Hex data mismatch!\nExpected: %s\nGot: %s",
|
|
hexExpectedEntropy,
|
|
hexData,
|
|
)
|
|
}
|
|
}
|
|
|
|
// TestInvalidParameters tests error conditions for parameter validation
|
|
func TestInvalidParameters(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
masterKey := mustParseTestMasterKey(t)
|
|
|
|
// Test cases for parameter validation
|
|
testCases := []struct {
|
|
name string
|
|
testFunc func() error
|
|
want error
|
|
}{
|
|
{
|
|
name: "BIP39 invalid word count",
|
|
testFunc: func() error {
|
|
// 13 is not valid (must be 12, 15, 18, 21, 24)
|
|
_, err := bip85.DeriveBIP39Entropy(masterKey, 0, 13, 0)
|
|
|
|
return err
|
|
},
|
|
want: bip85.ErrInvalidWordCount,
|
|
},
|
|
{
|
|
name: "Base64 password too short",
|
|
testFunc: func() error {
|
|
// Min is 20
|
|
_, err := bip85.DeriveBase64Password(masterKey, 19, 0)
|
|
|
|
return err
|
|
},
|
|
want: bip85.ErrInvalidBase64PwdLen,
|
|
},
|
|
{
|
|
name: "Base64 password too long",
|
|
testFunc: func() error {
|
|
// Max is 86
|
|
_, err := bip85.DeriveBase64Password(masterKey, 87, 0)
|
|
|
|
return err
|
|
},
|
|
want: bip85.ErrInvalidBase64PwdLen,
|
|
},
|
|
{
|
|
name: "Base85 password too short",
|
|
testFunc: func() error {
|
|
// Min is 10
|
|
_, err := bip85.DeriveBase85Password(masterKey, 9, 0)
|
|
|
|
return err
|
|
},
|
|
want: bip85.ErrInvalidBase85PwdLen,
|
|
},
|
|
{
|
|
name: "Base85 password too long",
|
|
testFunc: func() error {
|
|
// Max is 80
|
|
_, err := bip85.DeriveBase85Password(masterKey, 81, 0)
|
|
|
|
return err
|
|
},
|
|
want: bip85.ErrInvalidBase85PwdLen,
|
|
},
|
|
{
|
|
name: "Hex data too small",
|
|
testFunc: func() error {
|
|
// Min is 16
|
|
_, err := bip85.DeriveHex(masterKey, 15, 0)
|
|
|
|
return err
|
|
},
|
|
want: bip85.ErrInvalidNumBytes,
|
|
},
|
|
{
|
|
name: "Hex data too large",
|
|
testFunc: func() error {
|
|
// Max is 64
|
|
_, err := bip85.DeriveHex(masterKey, 65, 0)
|
|
|
|
return err
|
|
},
|
|
want: bip85.ErrInvalidNumBytes,
|
|
},
|
|
}
|
|
|
|
// Run all validation test cases
|
|
for _, tc := range testCases {
|
|
err := tc.testFunc()
|
|
if !errors.Is(err, tc.want) {
|
|
t.Errorf("Expected %v for %s, got %v", tc.want, tc.name, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestDeriveBIP85EntropyErrors checks that DeriveBIP85Entropy returns
|
|
// ErrNotPrivateKey for a public master key, and ErrInvalidPathComponent,
|
|
// wrapped, for a path component that is not a number.
|
|
func TestDeriveBIP85EntropyErrors(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
masterKey := mustParseTestMasterKey(t)
|
|
|
|
publicKey, err := masterKey.Neuter()
|
|
if err != nil {
|
|
t.Fatalf("Failed to get the public key of the master key: %v", err)
|
|
}
|
|
|
|
_, err = bip85.DeriveBIP85Entropy(publicKey, testCase1Path)
|
|
if !errors.Is(err, bip85.ErrNotPrivateKey) {
|
|
t.Errorf("Expected ErrNotPrivateKey, got %v", err)
|
|
}
|
|
|
|
_, err = bip85.DeriveBIP85Entropy(masterKey, bip85.BIP85_MASTER_PATH+"/x'")
|
|
if !errors.Is(err, bip85.ErrInvalidPathComponent) {
|
|
t.Errorf("Expected ErrInvalidPathComponent, got %v", err)
|
|
}
|
|
}
|
|
|
|
// TestAdditionalDeriveHex tests additional hex derivation scenarios
|
|
func TestAdditionalDeriveHex(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
logTestVector(t, "Additional Hex Derivation")
|
|
|
|
masterKey := mustParseTestMasterKey(t)
|
|
|
|
// Test min size (16 bytes)
|
|
hexMinBytes, err := bip85.DeriveHex(masterKey, 16, 0)
|
|
if err != nil {
|
|
t.Fatalf("Failed to derive 16-byte hex: %v", err)
|
|
}
|
|
|
|
t.Logf("16-byte hex: %s", hexMinBytes)
|
|
|
|
if len(hexMinBytes) != 32 { // 16 bytes = 32 hex chars
|
|
t.Errorf(
|
|
"Expected 32 hex chars (16 bytes), got %d chars",
|
|
len(hexMinBytes),
|
|
)
|
|
} else {
|
|
t.Logf("RESULT: PASS")
|
|
}
|
|
|
|
// Test max size (64 bytes)
|
|
hexMaxBytes, err := bip85.DeriveHex(masterKey, 64, 0)
|
|
if err != nil {
|
|
t.Fatalf("Failed to derive 64-byte hex: %v", err)
|
|
}
|
|
|
|
t.Logf("64-byte hex: %s", hexMaxBytes)
|
|
|
|
if len(hexMaxBytes) != 128 { // 64 bytes = 128 hex chars
|
|
t.Errorf(
|
|
"Expected 128 hex chars (64 bytes), got %d chars",
|
|
len(hexMaxBytes),
|
|
)
|
|
} else {
|
|
t.Logf("RESULT: PASS")
|
|
}
|
|
|
|
// Test different index values
|
|
hex1, err := bip85.DeriveHex(masterKey, 32, 0)
|
|
if err != nil {
|
|
t.Fatalf("Failed to derive hex with index 0: %v", err)
|
|
}
|
|
|
|
hex2, err := bip85.DeriveHex(masterKey, 32, 1)
|
|
if err != nil {
|
|
t.Fatalf("Failed to derive hex with index 1: %v", err)
|
|
}
|
|
|
|
t.Logf("Hex index 0: %s", hex1)
|
|
t.Logf("Hex index 1: %s", hex2)
|
|
|
|
if hex1 == hex2 {
|
|
t.Errorf("Expected different hex values for different indexes")
|
|
} else {
|
|
t.Logf("Different indexes produced different outputs: PASS")
|
|
}
|
|
}
|