All checks were successful
check / check (push) Successful in 2m0s
- 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 User-visible strings -------------------- No user-visible string changes remain. Every error message this branch composes is byte-identical to the one main composes. The err113 sentinels are shaped so that fmt.Errorf reassembles the original text around them: a sentinel carries the fixed words of the message and the caller supplies the interpolated value in the position it has always occupied. Where the value sits in the middle of the sentence the sentinel therefore holds only a fragment (for example vault.ErrVaultNotFound is "does not exist", composed by its caller as "vault <name> does not exist"); each such sentinel documents the message it participates in. Verified mechanically rather than by inspection: every fmt.Errorf and errors.New call site in both trees was parsed, the Error() text of any sentinel passed to %w substituted in, and the resulting sets of composed message templates compared. All 350 templates main produces are still produced, character for character; the set of messages lost or altered is empty. unlocker list ------------- findUnlockerIDByMetadata now returns (string, error) instead of signalling failure with an empty ID. An unreadable unlockers.d is no longer indistinguishable from "no matching entry", so UnlockersList skips the entry with a warning naming the directory, as it did before the scan was extracted into a helper, rather than emitting a row under a synthesized fallback ID that no unlocker remove or unlocker select can match and that suppresses the current-unlocker marker. The duplicate-check and shell-completion callers skip on the same condition, matching their pre-extraction behavior. Covered by tests in internal/cli/unlockers_list_test.go.
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")
|
|
}
|
|
}
|