All checks were successful
check / check (push) Successful in 1m8s
- Replace .golangci.yml with the canonical strict config (all linters enabled except the standard disable list; lll 88, funlen 80/50, cyclop 15, dupl 100; test files now linted) - Pin the Dockerfile lint stage to golangci/golangci-lint:v2.12.2 by tag and digest (Debian-based) - Fix all ~1550 findings surfaced by the new config: line wrapping, wsl_v5/nlreturn blank lines, noinlineerr splits, err113 sentinel errors, perfsprint/modernize rewrites, goconst constants, thelper, testifylint, noctx CommandContext, testpackage conversions, t.Parallel() where safe, and complexity/dupl helper extraction - Record the change and follow-up items in TODO.md
1166 lines
29 KiB
Go
1166 lines
29 KiB
Go
//nolint:gosec // G101: Test file contains BIP85 test vectors, not real credentials
|
|
package bip85_test
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/hex"
|
|
"fmt"
|
|
"strings"
|
|
"testing"
|
|
|
|
"git.eeqj.de/sneak/secret/pkg/bip85"
|
|
"github.com/btcsuite/btcd/btcutil/hdkeychain"
|
|
"github.com/tyler-smith/go-bip39"
|
|
)
|
|
|
|
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()
|
|
|
|
logTestVector(t, "Invalid Parameters")
|
|
|
|
masterKey := mustParseTestMasterKey(t)
|
|
|
|
// Test cases for parameter validation
|
|
testCases := []struct {
|
|
name string
|
|
testFunc func() 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
|
|
},
|
|
},
|
|
{
|
|
name: "Base64 password too short",
|
|
testFunc: func() error {
|
|
// Min is 20
|
|
_, err := bip85.DeriveBase64Password(masterKey, 19, 0)
|
|
|
|
return err
|
|
},
|
|
},
|
|
{
|
|
name: "Base64 password too long",
|
|
testFunc: func() error {
|
|
// Max is 86
|
|
_, err := bip85.DeriveBase64Password(masterKey, 87, 0)
|
|
|
|
return err
|
|
},
|
|
},
|
|
{
|
|
name: "Base85 password too short",
|
|
testFunc: func() error {
|
|
// Min is 10
|
|
_, err := bip85.DeriveBase85Password(masterKey, 9, 0)
|
|
|
|
return err
|
|
},
|
|
},
|
|
{
|
|
name: "Base85 password too long",
|
|
testFunc: func() error {
|
|
// Max is 80
|
|
_, err := bip85.DeriveBase85Password(masterKey, 81, 0)
|
|
|
|
return err
|
|
},
|
|
},
|
|
{
|
|
name: "Hex data too small",
|
|
testFunc: func() error {
|
|
// Min is 16
|
|
_, err := bip85.DeriveHex(masterKey, 15, 0)
|
|
|
|
return err
|
|
},
|
|
},
|
|
{
|
|
name: "Hex data too large",
|
|
testFunc: func() error {
|
|
// Max is 64
|
|
_, err := bip85.DeriveHex(masterKey, 65, 0)
|
|
|
|
return err
|
|
},
|
|
},
|
|
}
|
|
|
|
// Run all validation test cases
|
|
for _, tc := range testCases {
|
|
t.Logf("Testing: %s", tc.name)
|
|
|
|
err := tc.testFunc()
|
|
if err == nil {
|
|
t.Errorf("Expected error for %s, but got nil", tc.name)
|
|
} else {
|
|
t.Logf("Got expected error: %v", err)
|
|
t.Logf("RESULT: PASS")
|
|
}
|
|
}
|
|
}
|
|
|
|
// 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")
|
|
}
|
|
}
|