test: add offline integrity test suite with frozen vectors
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,4 +1,5 @@
|
||||
.venv/
|
||||
.venv312/
|
||||
__pycache__/
|
||||
*.pyc
|
||||
.vscode/
|
||||
|
||||
@@ -1,204 +0,0 @@
|
||||
diff --git a/src/pyhdwallet.py b/src/pyhdwallet.py
|
||||
index 1111111..2222222 100755
|
||||
--- a/src/pyhdwallet.py
|
||||
+++ b/src/pyhdwallet.py
|
||||
@@ -1,6 +1,6 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
-pyhdwallet v1.0.4 (Python 3.11+)
|
||||
+pyhdwallet v1.0.4 (Python 3.11+)
|
||||
|
||||
Commands:
|
||||
- fetchkey (online): download ASCII-armored PGP public key and print SHA256 + fingerprint
|
||||
@@ -33,6 +33,7 @@ import tempfile
|
||||
import urllib.request
|
||||
import warnings
|
||||
from pathlib import Path
|
||||
+from typing import Iterable
|
||||
from typing import Any, Dict, List, Optional, Tuple
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
@@ -112,6 +113,123 @@ def gen_base58_password(length: int) -> str:
|
||||
return "".join(secrets.choice(alphabet) for _ in range(length))
|
||||
|
||||
|
||||
+# -----------------------------------------------------------------------------
|
||||
+# Recover input helpers (interactive + stdin)
|
||||
+# -----------------------------------------------------------------------------
|
||||
+
|
||||
+
|
||||
+def _prompt_hidden_or_visible(prompt: str) -> str:
|
||||
+ """
|
||||
+ Prompt for sensitive input.
|
||||
+
|
||||
+ Prefer getpass (no echo). If getpass can't reliably hide input in the current
|
||||
+ environment, warn and fall back to visible input (user-selected behavior B).
|
||||
+ """
|
||||
+ try:
|
||||
+ with warnings.catch_warnings(record=True) as w:
|
||||
+ warnings.simplefilter("always")
|
||||
+ s = getpass.getpass(prompt)
|
||||
+ if w:
|
||||
+ print("⚠️ WARNING: getpass could not reliably hide input in this environment.", file=sys.stderr)
|
||||
+ print("⚠️ Falling back to visible input. Use a real terminal for hidden input.", file=sys.stderr)
|
||||
+ return input(prompt.replace("(hidden)", "(VISIBLE)"))
|
||||
+ return s
|
||||
+ except (Exception, KeyboardInterrupt):
|
||||
+ print("⚠️ WARNING: Hidden prompt failed; falling back to visible input.", file=sys.stderr)
|
||||
+ return input(prompt.replace("(hidden)", "(VISIBLE)"))
|
||||
+
|
||||
+
|
||||
+def _read_stdin_all() -> str:
|
||||
+ # Read all of stdin (for piping from secure sources).
|
||||
+ return sys.stdin.read().strip()
|
||||
+
|
||||
+
|
||||
+def _bip39_english_wordset() -> set[str]:
|
||||
+ """
|
||||
+ Load BIP39 English wordlist using bip_utils built-in word list getter.
|
||||
+ """
|
||||
+ from bip_utils import Bip39Languages, Bip39WordsListGetter
|
||||
+
|
||||
+ wl = Bip39WordsListGetter.GetByLanguage(Bip39Languages.ENGLISH)
|
||||
+ # wl provides the official 2048-word list via bip_utils. [web:342]
|
||||
+ return set(wl.GetWordList())
|
||||
+
|
||||
+
|
||||
+def _prompt_word_count() -> int:
|
||||
+ valid_counts = {12, 15, 18, 21, 24}
|
||||
+ while True:
|
||||
+ raw = input("Word count (12/15/18/21/24): ").strip()
|
||||
+ if raw.isdigit() and int(raw) in valid_counts:
|
||||
+ return int(raw)
|
||||
+ print("❌ Invalid word count. Choose 12/15/18/21/24.")
|
||||
+
|
||||
+
|
||||
+def interactive_mnemonic_word_by_word() -> str:
|
||||
+ """
|
||||
+ Guided mnemonic entry (English-only):
|
||||
+ - choose word count
|
||||
+ - enter each word with membership validation (✅/❌)
|
||||
+ - final checksum validation via Bip39MnemonicValidator
|
||||
+ """
|
||||
+ from bip_utils import Bip39MnemonicValidator
|
||||
+
|
||||
+ wordset = _bip39_english_wordset()
|
||||
+ n = _prompt_word_count()
|
||||
+
|
||||
+ words: List[str] = []
|
||||
+ i = 1
|
||||
+ while i <= n:
|
||||
+ w = _prompt_hidden_or_visible(f"{i}) Enter word (hidden): ").strip().lower()
|
||||
+ if w in wordset:
|
||||
+ print(f"{i}) ✅")
|
||||
+ words.append(w)
|
||||
+ i += 1
|
||||
+ else:
|
||||
+ print(f"{i}) ❌ Invalid BIP39 English word. Try again.")
|
||||
+
|
||||
+ mnemonic = " ".join(words)
|
||||
+ if not Bip39MnemonicValidator().IsValid(mnemonic):
|
||||
+ raise ValueError("Mnemonic checksum/format invalid (words valid but checksum failed).")
|
||||
+ return mnemonic
|
||||
+
|
||||
+
|
||||
# -----------------------------------------------------------------------------
|
||||
# Fingerprint
|
||||
# -----------------------------------------------------------------------------
|
||||
@@ -474,39 +592,52 @@ def cmd_recover(args) -> None:
|
||||
if args.off_screen:
|
||||
print("⚠️ Off-screen mode enabled: Sensitive data will not be printed to stdout.")
|
||||
|
||||
if args.export_private and not args.pgp_pubkey_file:
|
||||
raise ValueError("--export-private requires --pgp-pubkey-file")
|
||||
|
||||
from bip_utils import Bip39MnemonicValidator, Bip39SeedGenerator
|
||||
|
||||
- # Validate input methods
|
||||
- input_methods = [bool(args.mnemonic), bool(args.seed), args.interactive]
|
||||
- if sum(input_methods) > 1:
|
||||
- raise ValueError("Provide only one of --mnemonic, --seed, or --interactive")
|
||||
- if sum(input_methods) == 0:
|
||||
- raise ValueError("Missing input (use --mnemonic/--seed/--interactive)")
|
||||
-
|
||||
mnemonic: Optional[str] = None
|
||||
seed_hex: Optional[str] = None
|
||||
|
||||
- if args.interactive:
|
||||
- mode = input("Enter 'm' for mnemonic or 's' for seed: ").strip().lower()
|
||||
- if mode == "m":
|
||||
- mnemonic = getpass.getpass("BIP39 mnemonic (hidden): ").strip()
|
||||
- elif mode == "s":
|
||||
- seed_hex = getpass.getpass("Seed hex (hidden, 128 hex chars): ").strip()
|
||||
- else:
|
||||
- raise ValueError("Invalid choice")
|
||||
- elif args.mnemonic:
|
||||
- mnemonic = args.mnemonic.strip()
|
||||
- else:
|
||||
- seed_hex = args.seed.strip()
|
||||
+ # New input model: exactly one of:
|
||||
+ # --interactive (guided mnemonic only, English-only)
|
||||
+ # --mnemonic-stdin (read full phrase from stdin)
|
||||
+ # --seed-stdin (read seed hex from stdin)
|
||||
+ if args.interactive:
|
||||
+ mnemonic = interactive_mnemonic_word_by_word()
|
||||
+ elif args.mnemonic_stdin:
|
||||
+ mnemonic = _read_stdin_all()
|
||||
+ if not mnemonic:
|
||||
+ raise ValueError("Empty stdin for --mnemonic-stdin")
|
||||
+ elif args.seed_stdin:
|
||||
+ seed_hex = _read_stdin_all()
|
||||
+ if not seed_hex:
|
||||
+ raise ValueError("Empty stdin for --seed-stdin")
|
||||
+ else:
|
||||
+ raise ValueError("Missing input mode (use --interactive / --mnemonic-stdin / --seed-stdin)")
|
||||
|
||||
passphrase = getpass.getpass("Enter BIP39 passphrase (hidden): ") if args.passphrase else ""
|
||||
|
||||
if mnemonic:
|
||||
if not Bip39MnemonicValidator().IsValid(mnemonic):
|
||||
raise ValueError("Invalid BIP39 mnemonic")
|
||||
seed_bytes = Bip39SeedGenerator(mnemonic).Generate(passphrase)
|
||||
else:
|
||||
b = bytes.fromhex(seed_hex or "")
|
||||
if len(b) != 64:
|
||||
raise ValueError("Invalid seed hex (expected 64 bytes / 128 hex chars)")
|
||||
seed_bytes = b
|
||||
|
||||
fp = get_master_fingerprint(seed_bytes)
|
||||
@@ -676,18 +807,30 @@ def build_parser() -> argparse.ArgumentParser:
|
||||
|
||||
# recover
|
||||
p_rec = sub.add_parser("recover", help="Recover addresses from mnemonic/seed (offline)")
|
||||
add_common(p_rec)
|
||||
- p_rec.add_argument("--mnemonic", default="")
|
||||
- p_rec.add_argument("--seed", default="")
|
||||
- p_rec.add_argument("--interactive", action="store_true")
|
||||
+
|
||||
+ # New safer input model for recover:
|
||||
+ # - No plaintext secrets in CLI args.
|
||||
+ # - Use guided interactive entry or stdin.
|
||||
+ g = p_rec.add_mutually_exclusive_group(required=True)
|
||||
+ g.add_argument(
|
||||
+ "--interactive",
|
||||
+ action="store_true",
|
||||
+ help="Guided mnemonic entry (English-only, per-word validation)",
|
||||
+ )
|
||||
+ g.add_argument(
|
||||
+ "--mnemonic-stdin",
|
||||
+ dest="mnemonic_stdin",
|
||||
+ action="store_true",
|
||||
+ help="Read BIP39 mnemonic from stdin (non-interactive)",
|
||||
+ )
|
||||
+ g.add_argument(
|
||||
+ "--seed-stdin",
|
||||
+ dest="seed_stdin",
|
||||
+ action="store_true",
|
||||
+ help="Read seed hex (128 hex chars) from stdin (non-interactive)",
|
||||
+ )
|
||||
|
||||
# test
|
||||
p_test = sub.add_parser("test", help="Run minimal offline tests")
|
||||
p_test.add_argument("--off-screen", action="store_true", help="Enable off-screen mode: no printing of sensitive data to stdout.")
|
||||
|
||||
return parser
|
||||
104
tests/bootstrap_vectors.py
Normal file
104
tests/bootstrap_vectors.py
Normal file
@@ -0,0 +1,104 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Bootstrap script to generate tests/vectors.json.
|
||||
Run this ONCE (online allowed if needed, though this logic is offline)
|
||||
to freeze the expected outputs of the current pyhdwallet implementation.
|
||||
"""
|
||||
import sys
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
# Add src to path so we can import pyhdwallet
|
||||
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "../src")))
|
||||
|
||||
import pyhdwallet
|
||||
from bip_utils import Bip39SeedGenerator
|
||||
|
||||
def main():
|
||||
print("Bootstrapping test vectors...")
|
||||
vectors = {}
|
||||
|
||||
# 1. PGP Fingerprint Vector
|
||||
recipient_path = Path(__file__).parent / "data" / "recipient.asc"
|
||||
if not recipient_path.exists():
|
||||
print(f"Error: {recipient_path} not found. Please create it first.")
|
||||
sys.exit(1)
|
||||
|
||||
with open(recipient_path, "r", encoding="utf-8") as f:
|
||||
armored = f.read()
|
||||
|
||||
# Calculate expected fingerprint using current code logic
|
||||
expected_fpr = pyhdwallet.pgp_fingerprint(armored)
|
||||
|
||||
vectors["pgp"] = {
|
||||
"recipient_file": "recipient.asc",
|
||||
"expected_fingerprint": expected_fpr
|
||||
}
|
||||
print(f"PGP: Pinned fingerprint {expected_fpr}")
|
||||
|
||||
# 2. BIP39 & Derivation Vectors
|
||||
mnemonic = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about"
|
||||
|
||||
vectors["bip39"] = []
|
||||
|
||||
# Case A: No Passphrase
|
||||
seed_bytes = Bip39SeedGenerator(mnemonic).Generate("")
|
||||
seed_hex = seed_bytes.hex()
|
||||
|
||||
# Generate addresses for all supported chains/profiles
|
||||
chains = ["ethereum", "bitcoin", "solana"]
|
||||
# Fixed profile names to use underscores instead of hyphens
|
||||
sol_profiles = ["phantom_bip44change", "phantom_bip44", "solana_bip39_first32"]
|
||||
|
||||
derived_data = {}
|
||||
|
||||
# Run derivation for each sol profile logic
|
||||
for profile in sol_profiles:
|
||||
res = pyhdwallet.derive_all(
|
||||
seed_bytes,
|
||||
chains,
|
||||
count=5,
|
||||
sol_profile=profile,
|
||||
export_private=False
|
||||
)
|
||||
derived_data[profile] = res["addresses"]
|
||||
|
||||
vectors["bip39"].append({
|
||||
"mnemonic": mnemonic,
|
||||
"passphrase": "",
|
||||
"expected_seed_hex": seed_hex,
|
||||
"derived_addresses": derived_data
|
||||
})
|
||||
|
||||
# Case B: With Passphrase (Regression test)
|
||||
passphrase = "TREZOR"
|
||||
seed_bytes_p = Bip39SeedGenerator(mnemonic).Generate(passphrase)
|
||||
seed_hex_p = seed_bytes_p.hex()
|
||||
|
||||
res_p = pyhdwallet.derive_all(
|
||||
seed_bytes_p,
|
||||
chains,
|
||||
count=1,
|
||||
sol_profile="phantom_bip44change",
|
||||
export_private=False
|
||||
)
|
||||
|
||||
vectors["bip39"].append({
|
||||
"mnemonic": mnemonic,
|
||||
"passphrase": passphrase,
|
||||
"expected_seed_hex": seed_hex_p,
|
||||
"derived_addresses": {"phantom_bip44change": res_p["addresses"]}
|
||||
})
|
||||
|
||||
print("Derivation: Generated vectors for empty and non-empty passphrases.")
|
||||
|
||||
# 3. Save
|
||||
out_path = Path(__file__).parent / "vectors.json"
|
||||
with open(out_path, "w", encoding="utf-8") as f:
|
||||
json.dump(vectors, f, indent=2)
|
||||
|
||||
print(f"Success! Vectors written to {out_path}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
136
tests/test_vectors.py
Normal file
136
tests/test_vectors.py
Normal file
@@ -0,0 +1,136 @@
|
||||
import sys
|
||||
import os
|
||||
import json
|
||||
import pytest
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
# Add src to path at the very top
|
||||
TEST_DIR = Path(__file__).parent
|
||||
SRC_DIR = TEST_DIR.parent / "src"
|
||||
sys.path.insert(0, str(SRC_DIR))
|
||||
|
||||
import pyhdwallet
|
||||
from bip_utils import Bip39SeedGenerator
|
||||
|
||||
DATA_DIR = TEST_DIR / "data"
|
||||
VECTORS_FILE = TEST_DIR / "vectors.json"
|
||||
|
||||
@pytest.fixture
|
||||
def vectors():
|
||||
if not VECTORS_FILE.exists():
|
||||
pytest.fail("tests/vectors.json missing. Run tests/bootstrap_vectors.py first.")
|
||||
with open(VECTORS_FILE, "r") as f:
|
||||
return json.load(f)
|
||||
|
||||
@pytest.fixture
|
||||
def recipient_key_content():
|
||||
path = DATA_DIR / "recipient.asc"
|
||||
if not path.exists():
|
||||
pytest.fail("tests/data/recipient.asc missing.")
|
||||
with open(path, "r", encoding="utf-8") as f:
|
||||
return f.read()
|
||||
|
||||
def test_pgp_fingerprint_calculation(vectors, recipient_key_content):
|
||||
"""
|
||||
Verifies that pgp_fingerprint computes the expected fingerprint
|
||||
for the stored recipient key.
|
||||
"""
|
||||
expected = vectors["pgp"]["expected_fingerprint"]
|
||||
actual = pyhdwallet.pgp_fingerprint(recipient_key_content)
|
||||
assert actual == expected, "Fingerprint calculation logic has changed!"
|
||||
|
||||
def test_pgp_require_fingerprint_match(vectors):
|
||||
"""
|
||||
Verifies the safety check require_fingerprint_match enforces exact matches.
|
||||
"""
|
||||
expected = vectors["pgp"]["expected_fingerprint"]
|
||||
|
||||
# Should not raise
|
||||
pyhdwallet.require_fingerprint_match(expected, expected, "test")
|
||||
|
||||
# Should raise on mismatch
|
||||
wrong = expected.replace("A", "B") if "A" in expected else expected.replace("0", "1")
|
||||
with pytest.raises(ValueError, match="test: PGP fingerprint mismatch"):
|
||||
pyhdwallet.require_fingerprint_match(wrong, expected, "test")
|
||||
|
||||
def test_bip39_seed_derivation(vectors):
|
||||
"""
|
||||
Verifies that mnemonics convert to seeds exactly as they did at bootstrap time.
|
||||
"""
|
||||
for case in vectors["bip39"]:
|
||||
mnemonic = case["mnemonic"]
|
||||
passphrase = case["passphrase"]
|
||||
expected_hex = case["expected_seed_hex"]
|
||||
|
||||
# Verify internal Bip39SeedGenerator usage matches expected hex
|
||||
actual_bytes = Bip39SeedGenerator(mnemonic).Generate(passphrase)
|
||||
assert actual_bytes.hex() == expected_hex
|
||||
|
||||
def test_address_derivation_integrity(vectors):
|
||||
"""
|
||||
Verifies derive_all produces the exact same addresses for supported chains.
|
||||
"""
|
||||
for case in vectors["bip39"]:
|
||||
seed_hex = case["expected_seed_hex"]
|
||||
seed_bytes = bytes.fromhex(seed_hex)
|
||||
|
||||
for profile, expected_addresses in case["derived_addresses"].items():
|
||||
# Infer count from ethereum addresses in the expected data
|
||||
# (Ethereum always generates `count` addresses, unlike Solana which depends on profile)
|
||||
count = len(expected_addresses.get("ethereum", []))
|
||||
|
||||
result = pyhdwallet.derive_all(
|
||||
seed_bytes,
|
||||
chains=["ethereum", "bitcoin", "solana"],
|
||||
count=count,
|
||||
sol_profile=profile,
|
||||
export_private=False
|
||||
)
|
||||
assert result["addresses"] == expected_addresses, f"Mismatch for profile {profile}"
|
||||
|
||||
def test_cli_recover_smoke(tmp_path, vectors):
|
||||
"""
|
||||
Runs the CLI in a subprocess to verify end-to-end wiring
|
||||
without network (recover mode).
|
||||
"""
|
||||
vector = vectors["bip39"][0]
|
||||
mnemonic = vector["mnemonic"]
|
||||
expected_fp = vectors["pgp"]["expected_fingerprint"]
|
||||
recipient_file = DATA_DIR / "recipient.asc"
|
||||
|
||||
# 1. Successful Recovery
|
||||
cmd = [
|
||||
sys.executable, "src/pyhdwallet.py", "recover",
|
||||
"--mnemonic-stdin",
|
||||
"--pgp-pubkey-file", str(recipient_file),
|
||||
"--expected-fingerprint", expected_fp,
|
||||
"--file",
|
||||
"--wallet-location", str(tmp_path),
|
||||
"--off-screen",
|
||||
"--zip-password-mode", "auto"
|
||||
]
|
||||
|
||||
result = subprocess.run(
|
||||
cmd,
|
||||
input=mnemonic.encode("utf-8"),
|
||||
capture_output=True
|
||||
)
|
||||
|
||||
assert result.returncode == 0, f"CLI failed: {result.stderr.decode()}"
|
||||
zips = list(tmp_path.glob("*.zip"))
|
||||
assert len(zips) == 1, "Expected exactly one zip file output"
|
||||
|
||||
# 2. Failed Recovery (Wrong Fingerprint)
|
||||
wrong_fp = expected_fp.replace("A", "B")
|
||||
cmd_fail = list(cmd)
|
||||
cmd_fail[cmd_fail.index(expected_fp)] = wrong_fp
|
||||
|
||||
result_fail = subprocess.run(
|
||||
cmd_fail,
|
||||
input=mnemonic.encode("utf-8"),
|
||||
capture_output=True
|
||||
)
|
||||
|
||||
assert result_fail.returncode != 0
|
||||
assert b"fingerprint mismatch" in result_fail.stderr or b"fingerprint mismatch" in result_fail.stdout
|
||||
482
tests/vectors.json
Normal file
482
tests/vectors.json
Normal file
@@ -0,0 +1,482 @@
|
||||
{
|
||||
"pgp": {
|
||||
"recipient_file": "recipient.asc",
|
||||
"expected_fingerprint": "A27B96F2B169B5491013D2DA892B822C14A9AA18"
|
||||
},
|
||||
"bip39": [
|
||||
{
|
||||
"mnemonic": "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about",
|
||||
"passphrase": "",
|
||||
"expected_seed_hex": "5eb00bbddcf069084889a8ab9155568165f5c453ccb85e70811aaed6f6da5fc19a5ac40b389cd370d086206dec8aa6c43daea6690f20ad3d8d48b2d2ce9e38e4",
|
||||
"derived_addresses": {
|
||||
"phantom_bip44change": {
|
||||
"ethereum": [
|
||||
{
|
||||
"index": 0,
|
||||
"path": "m/44'/60'/0'/0/0",
|
||||
"address": "0x9858EfFD232B4033E47d90003D41EC34EcaEda94"
|
||||
},
|
||||
{
|
||||
"index": 1,
|
||||
"path": "m/44'/60'/0'/0/1",
|
||||
"address": "0x6Fac4D18c912343BF86fa7049364Dd4E424Ab9C0"
|
||||
},
|
||||
{
|
||||
"index": 2,
|
||||
"path": "m/44'/60'/0'/0/2",
|
||||
"address": "0xb6716976A3ebe8D39aCEB04372f22Ff8e6802D7A"
|
||||
},
|
||||
{
|
||||
"index": 3,
|
||||
"path": "m/44'/60'/0'/0/3",
|
||||
"address": "0xF3f50213C1d2e255e4B2bAD430F8A38EEF8D718E"
|
||||
},
|
||||
{
|
||||
"index": 4,
|
||||
"path": "m/44'/60'/0'/0/4",
|
||||
"address": "0x51cA8ff9f1C0a99f88E86B8112eA3237F55374cA"
|
||||
}
|
||||
],
|
||||
"solana": [
|
||||
{
|
||||
"index": 0,
|
||||
"path": "m/44'/501'/0'/0'",
|
||||
"address": "HAgk14JpMQLgt6rVgv7cBQFJWFto5Dqxi472uT3DKpqk"
|
||||
},
|
||||
{
|
||||
"index": 1,
|
||||
"path": "m/44'/501'/1'/0'",
|
||||
"address": "Hh8QwFUA6MtVu1qAoq12ucvFHNwCcVTV7hpWjeY1Hztb"
|
||||
},
|
||||
{
|
||||
"index": 2,
|
||||
"path": "m/44'/501'/2'/0'",
|
||||
"address": "7WktogJEd2wQ9eH2oWusmcoFTgeYi6rS632UviTBJ2jm"
|
||||
},
|
||||
{
|
||||
"index": 3,
|
||||
"path": "m/44'/501'/3'/0'",
|
||||
"address": "3YqEpfo3c818GhvbQ1UmVY1nJxw16vtu4JB9peJXT94k"
|
||||
},
|
||||
{
|
||||
"index": 4,
|
||||
"path": "m/44'/501'/4'/0'",
|
||||
"address": "6nod592sTfEWD3VSVPdQndLMVBCNmMc6ngt7MyGBK21j"
|
||||
}
|
||||
],
|
||||
"bitcoin": [
|
||||
{
|
||||
"index": 0,
|
||||
"path": "m/84'/0'/0'/0/0",
|
||||
"address_type": "native_segwit",
|
||||
"address": "bc1qcr8te4kr609gcawutmrza0j4xv80jy8z306fyu"
|
||||
},
|
||||
{
|
||||
"index": 0,
|
||||
"path": "m/49'/0'/0'/0/0",
|
||||
"address_type": "segwit",
|
||||
"address": "37VucYSaXLCAsxYyAPfbSi9eh4iEcbShgf"
|
||||
},
|
||||
{
|
||||
"index": 0,
|
||||
"path": "m/44'/0'/0'/0/0",
|
||||
"address_type": "legacy",
|
||||
"address": "1LqBGSKuX5yYUonjxT5qGfpUsXKYYWeabA"
|
||||
},
|
||||
{
|
||||
"index": 1,
|
||||
"path": "m/84'/0'/0'/0/1",
|
||||
"address_type": "native_segwit",
|
||||
"address": "bc1qnjg0jd8228aq7egyzacy8cys3knf9xvrerkf9g"
|
||||
},
|
||||
{
|
||||
"index": 1,
|
||||
"path": "m/49'/0'/0'/0/1",
|
||||
"address_type": "segwit",
|
||||
"address": "3LtMnn87fqUeHBUG414p9CWwnoV6E2pNKS"
|
||||
},
|
||||
{
|
||||
"index": 1,
|
||||
"path": "m/44'/0'/0'/0/1",
|
||||
"address_type": "legacy",
|
||||
"address": "1Ak8PffB2meyfYnbXZR9EGfLfFZVpzJvQP"
|
||||
},
|
||||
{
|
||||
"index": 2,
|
||||
"path": "m/84'/0'/0'/0/2",
|
||||
"address_type": "native_segwit",
|
||||
"address": "bc1qp59yckz4ae5c4efgw2s5wfyvrz0ala7rgvuz8z"
|
||||
},
|
||||
{
|
||||
"index": 2,
|
||||
"path": "m/49'/0'/0'/0/2",
|
||||
"address_type": "segwit",
|
||||
"address": "3B4cvWGR8X6Xs8nvTxVUoMJV77E4f7oaia"
|
||||
},
|
||||
{
|
||||
"index": 2,
|
||||
"path": "m/44'/0'/0'/0/2",
|
||||
"address_type": "legacy",
|
||||
"address": "1MNF5RSaabFwcbtJirJwKnDytsXXEsVsNb"
|
||||
},
|
||||
{
|
||||
"index": 3,
|
||||
"path": "m/84'/0'/0'/0/3",
|
||||
"address_type": "native_segwit",
|
||||
"address": "bc1qgl5vlg0zdl7yvprgxj9fevsc6q6x5dmcyk3cn3"
|
||||
},
|
||||
{
|
||||
"index": 3,
|
||||
"path": "m/49'/0'/0'/0/3",
|
||||
"address_type": "segwit",
|
||||
"address": "38CahkVftQneLonbWtfWxiiaT2fdnzsEAN"
|
||||
},
|
||||
{
|
||||
"index": 3,
|
||||
"path": "m/44'/0'/0'/0/3",
|
||||
"address_type": "legacy",
|
||||
"address": "1MVGa13XFvvpKGZdX389iU8b3qwtmAyrsJ"
|
||||
},
|
||||
{
|
||||
"index": 4,
|
||||
"path": "m/84'/0'/0'/0/4",
|
||||
"address_type": "native_segwit",
|
||||
"address": "bc1qm97vqzgj934vnaq9s53ynkyf9dgr05rargr04n"
|
||||
},
|
||||
{
|
||||
"index": 4,
|
||||
"path": "m/49'/0'/0'/0/4",
|
||||
"address_type": "segwit",
|
||||
"address": "37mbeJptxfQC6SNNLJ9a8efCY4BwBh5Kak"
|
||||
},
|
||||
{
|
||||
"index": 4,
|
||||
"path": "m/44'/0'/0'/0/4",
|
||||
"address_type": "legacy",
|
||||
"address": "1Gka4JdwhLxRwXaC6oLNH4YuEogeeSwqW7"
|
||||
}
|
||||
]
|
||||
},
|
||||
"phantom_bip44": {
|
||||
"ethereum": [
|
||||
{
|
||||
"index": 0,
|
||||
"path": "m/44'/60'/0'/0/0",
|
||||
"address": "0x9858EfFD232B4033E47d90003D41EC34EcaEda94"
|
||||
},
|
||||
{
|
||||
"index": 1,
|
||||
"path": "m/44'/60'/0'/0/1",
|
||||
"address": "0x6Fac4D18c912343BF86fa7049364Dd4E424Ab9C0"
|
||||
},
|
||||
{
|
||||
"index": 2,
|
||||
"path": "m/44'/60'/0'/0/2",
|
||||
"address": "0xb6716976A3ebe8D39aCEB04372f22Ff8e6802D7A"
|
||||
},
|
||||
{
|
||||
"index": 3,
|
||||
"path": "m/44'/60'/0'/0/3",
|
||||
"address": "0xF3f50213C1d2e255e4B2bAD430F8A38EEF8D718E"
|
||||
},
|
||||
{
|
||||
"index": 4,
|
||||
"path": "m/44'/60'/0'/0/4",
|
||||
"address": "0x51cA8ff9f1C0a99f88E86B8112eA3237F55374cA"
|
||||
}
|
||||
],
|
||||
"solana": [
|
||||
{
|
||||
"index": 0,
|
||||
"path": "m/44'/501'/0'",
|
||||
"address": "GjJyeC1r2RgkuoCWMyPYkCWSGSGLcz266EaAkLA27AhL"
|
||||
},
|
||||
{
|
||||
"index": 1,
|
||||
"path": "m/44'/501'/1'",
|
||||
"address": "ANf3TEKFL6jPWjzkndo4CbnNdUNkBk4KHPggJs2nu8Xi"
|
||||
},
|
||||
{
|
||||
"index": 2,
|
||||
"path": "m/44'/501'/2'",
|
||||
"address": "Ag74i82rUZBTgMGLacCA1ZLnotvAca8CLscXcrG6Nwem"
|
||||
},
|
||||
{
|
||||
"index": 3,
|
||||
"path": "m/44'/501'/3'",
|
||||
"address": "weCFpgyyyrzum6nA8XdmJXjDGDTXmG5P2DdgHv59hgQ"
|
||||
},
|
||||
{
|
||||
"index": 4,
|
||||
"path": "m/44'/501'/4'",
|
||||
"address": "4w6V162fV7HJQNma7vZvxjunqmkie8hM2x1DqaFQqxqL"
|
||||
}
|
||||
],
|
||||
"bitcoin": [
|
||||
{
|
||||
"index": 0,
|
||||
"path": "m/84'/0'/0'/0/0",
|
||||
"address_type": "native_segwit",
|
||||
"address": "bc1qcr8te4kr609gcawutmrza0j4xv80jy8z306fyu"
|
||||
},
|
||||
{
|
||||
"index": 0,
|
||||
"path": "m/49'/0'/0'/0/0",
|
||||
"address_type": "segwit",
|
||||
"address": "37VucYSaXLCAsxYyAPfbSi9eh4iEcbShgf"
|
||||
},
|
||||
{
|
||||
"index": 0,
|
||||
"path": "m/44'/0'/0'/0/0",
|
||||
"address_type": "legacy",
|
||||
"address": "1LqBGSKuX5yYUonjxT5qGfpUsXKYYWeabA"
|
||||
},
|
||||
{
|
||||
"index": 1,
|
||||
"path": "m/84'/0'/0'/0/1",
|
||||
"address_type": "native_segwit",
|
||||
"address": "bc1qnjg0jd8228aq7egyzacy8cys3knf9xvrerkf9g"
|
||||
},
|
||||
{
|
||||
"index": 1,
|
||||
"path": "m/49'/0'/0'/0/1",
|
||||
"address_type": "segwit",
|
||||
"address": "3LtMnn87fqUeHBUG414p9CWwnoV6E2pNKS"
|
||||
},
|
||||
{
|
||||
"index": 1,
|
||||
"path": "m/44'/0'/0'/0/1",
|
||||
"address_type": "legacy",
|
||||
"address": "1Ak8PffB2meyfYnbXZR9EGfLfFZVpzJvQP"
|
||||
},
|
||||
{
|
||||
"index": 2,
|
||||
"path": "m/84'/0'/0'/0/2",
|
||||
"address_type": "native_segwit",
|
||||
"address": "bc1qp59yckz4ae5c4efgw2s5wfyvrz0ala7rgvuz8z"
|
||||
},
|
||||
{
|
||||
"index": 2,
|
||||
"path": "m/49'/0'/0'/0/2",
|
||||
"address_type": "segwit",
|
||||
"address": "3B4cvWGR8X6Xs8nvTxVUoMJV77E4f7oaia"
|
||||
},
|
||||
{
|
||||
"index": 2,
|
||||
"path": "m/44'/0'/0'/0/2",
|
||||
"address_type": "legacy",
|
||||
"address": "1MNF5RSaabFwcbtJirJwKnDytsXXEsVsNb"
|
||||
},
|
||||
{
|
||||
"index": 3,
|
||||
"path": "m/84'/0'/0'/0/3",
|
||||
"address_type": "native_segwit",
|
||||
"address": "bc1qgl5vlg0zdl7yvprgxj9fevsc6q6x5dmcyk3cn3"
|
||||
},
|
||||
{
|
||||
"index": 3,
|
||||
"path": "m/49'/0'/0'/0/3",
|
||||
"address_type": "segwit",
|
||||
"address": "38CahkVftQneLonbWtfWxiiaT2fdnzsEAN"
|
||||
},
|
||||
{
|
||||
"index": 3,
|
||||
"path": "m/44'/0'/0'/0/3",
|
||||
"address_type": "legacy",
|
||||
"address": "1MVGa13XFvvpKGZdX389iU8b3qwtmAyrsJ"
|
||||
},
|
||||
{
|
||||
"index": 4,
|
||||
"path": "m/84'/0'/0'/0/4",
|
||||
"address_type": "native_segwit",
|
||||
"address": "bc1qm97vqzgj934vnaq9s53ynkyf9dgr05rargr04n"
|
||||
},
|
||||
{
|
||||
"index": 4,
|
||||
"path": "m/49'/0'/0'/0/4",
|
||||
"address_type": "segwit",
|
||||
"address": "37mbeJptxfQC6SNNLJ9a8efCY4BwBh5Kak"
|
||||
},
|
||||
{
|
||||
"index": 4,
|
||||
"path": "m/44'/0'/0'/0/4",
|
||||
"address_type": "legacy",
|
||||
"address": "1Gka4JdwhLxRwXaC6oLNH4YuEogeeSwqW7"
|
||||
}
|
||||
]
|
||||
},
|
||||
"solana_bip39_first32": {
|
||||
"ethereum": [
|
||||
{
|
||||
"index": 0,
|
||||
"path": "m/44'/60'/0'/0/0",
|
||||
"address": "0x9858EfFD232B4033E47d90003D41EC34EcaEda94"
|
||||
},
|
||||
{
|
||||
"index": 1,
|
||||
"path": "m/44'/60'/0'/0/1",
|
||||
"address": "0x6Fac4D18c912343BF86fa7049364Dd4E424Ab9C0"
|
||||
},
|
||||
{
|
||||
"index": 2,
|
||||
"path": "m/44'/60'/0'/0/2",
|
||||
"address": "0xb6716976A3ebe8D39aCEB04372f22Ff8e6802D7A"
|
||||
},
|
||||
{
|
||||
"index": 3,
|
||||
"path": "m/44'/60'/0'/0/3",
|
||||
"address": "0xF3f50213C1d2e255e4B2bAD430F8A38EEF8D718E"
|
||||
},
|
||||
{
|
||||
"index": 4,
|
||||
"path": "m/44'/60'/0'/0/4",
|
||||
"address": "0x51cA8ff9f1C0a99f88E86B8112eA3237F55374cA"
|
||||
}
|
||||
],
|
||||
"solana": [
|
||||
{
|
||||
"index": 0,
|
||||
"path": "BIP39 seed[0:32]",
|
||||
"address": "EHqmfkN89RJ7Y33CXM6uCzhVeuywHoJXZZLszBHHZy7o"
|
||||
}
|
||||
],
|
||||
"bitcoin": [
|
||||
{
|
||||
"index": 0,
|
||||
"path": "m/84'/0'/0'/0/0",
|
||||
"address_type": "native_segwit",
|
||||
"address": "bc1qcr8te4kr609gcawutmrza0j4xv80jy8z306fyu"
|
||||
},
|
||||
{
|
||||
"index": 0,
|
||||
"path": "m/49'/0'/0'/0/0",
|
||||
"address_type": "segwit",
|
||||
"address": "37VucYSaXLCAsxYyAPfbSi9eh4iEcbShgf"
|
||||
},
|
||||
{
|
||||
"index": 0,
|
||||
"path": "m/44'/0'/0'/0/0",
|
||||
"address_type": "legacy",
|
||||
"address": "1LqBGSKuX5yYUonjxT5qGfpUsXKYYWeabA"
|
||||
},
|
||||
{
|
||||
"index": 1,
|
||||
"path": "m/84'/0'/0'/0/1",
|
||||
"address_type": "native_segwit",
|
||||
"address": "bc1qnjg0jd8228aq7egyzacy8cys3knf9xvrerkf9g"
|
||||
},
|
||||
{
|
||||
"index": 1,
|
||||
"path": "m/49'/0'/0'/0/1",
|
||||
"address_type": "segwit",
|
||||
"address": "3LtMnn87fqUeHBUG414p9CWwnoV6E2pNKS"
|
||||
},
|
||||
{
|
||||
"index": 1,
|
||||
"path": "m/44'/0'/0'/0/1",
|
||||
"address_type": "legacy",
|
||||
"address": "1Ak8PffB2meyfYnbXZR9EGfLfFZVpzJvQP"
|
||||
},
|
||||
{
|
||||
"index": 2,
|
||||
"path": "m/84'/0'/0'/0/2",
|
||||
"address_type": "native_segwit",
|
||||
"address": "bc1qp59yckz4ae5c4efgw2s5wfyvrz0ala7rgvuz8z"
|
||||
},
|
||||
{
|
||||
"index": 2,
|
||||
"path": "m/49'/0'/0'/0/2",
|
||||
"address_type": "segwit",
|
||||
"address": "3B4cvWGR8X6Xs8nvTxVUoMJV77E4f7oaia"
|
||||
},
|
||||
{
|
||||
"index": 2,
|
||||
"path": "m/44'/0'/0'/0/2",
|
||||
"address_type": "legacy",
|
||||
"address": "1MNF5RSaabFwcbtJirJwKnDytsXXEsVsNb"
|
||||
},
|
||||
{
|
||||
"index": 3,
|
||||
"path": "m/84'/0'/0'/0/3",
|
||||
"address_type": "native_segwit",
|
||||
"address": "bc1qgl5vlg0zdl7yvprgxj9fevsc6q6x5dmcyk3cn3"
|
||||
},
|
||||
{
|
||||
"index": 3,
|
||||
"path": "m/49'/0'/0'/0/3",
|
||||
"address_type": "segwit",
|
||||
"address": "38CahkVftQneLonbWtfWxiiaT2fdnzsEAN"
|
||||
},
|
||||
{
|
||||
"index": 3,
|
||||
"path": "m/44'/0'/0'/0/3",
|
||||
"address_type": "legacy",
|
||||
"address": "1MVGa13XFvvpKGZdX389iU8b3qwtmAyrsJ"
|
||||
},
|
||||
{
|
||||
"index": 4,
|
||||
"path": "m/84'/0'/0'/0/4",
|
||||
"address_type": "native_segwit",
|
||||
"address": "bc1qm97vqzgj934vnaq9s53ynkyf9dgr05rargr04n"
|
||||
},
|
||||
{
|
||||
"index": 4,
|
||||
"path": "m/49'/0'/0'/0/4",
|
||||
"address_type": "segwit",
|
||||
"address": "37mbeJptxfQC6SNNLJ9a8efCY4BwBh5Kak"
|
||||
},
|
||||
{
|
||||
"index": 4,
|
||||
"path": "m/44'/0'/0'/0/4",
|
||||
"address_type": "legacy",
|
||||
"address": "1Gka4JdwhLxRwXaC6oLNH4YuEogeeSwqW7"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"mnemonic": "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about",
|
||||
"passphrase": "TREZOR",
|
||||
"expected_seed_hex": "c55257c360c07c72029aebc1b53c05ed0362ada38ead3e3e9efa3708e53495531f09a6987599d18264c1e1c92f2cf141630c7a3c4ab7c81b2f001698e7463b04",
|
||||
"derived_addresses": {
|
||||
"phantom_bip44change": {
|
||||
"ethereum": [
|
||||
{
|
||||
"index": 0,
|
||||
"path": "m/44'/60'/0'/0/0",
|
||||
"address": "0x9c32F71D4DB8Fb9e1A58B0a80dF79935e7256FA6"
|
||||
}
|
||||
],
|
||||
"solana": [
|
||||
{
|
||||
"index": 0,
|
||||
"path": "m/44'/501'/0'/0'",
|
||||
"address": "7zSmbu6gKkb6HB7UDPtHYjwCWuBHU1D4TpNZFm4sndQe"
|
||||
}
|
||||
],
|
||||
"bitcoin": [
|
||||
{
|
||||
"index": 0,
|
||||
"path": "m/84'/0'/0'/0/0",
|
||||
"address_type": "native_segwit",
|
||||
"address": "bc1qv5rmq0kt9yz3pm36wvzct7p3x6mtgehjul0feu"
|
||||
},
|
||||
{
|
||||
"index": 0,
|
||||
"path": "m/49'/0'/0'/0/0",
|
||||
"address_type": "segwit",
|
||||
"address": "3Aho3kS7vgVWKTpRHjcqBoPXiCujiSuTaZ"
|
||||
},
|
||||
{
|
||||
"index": 0,
|
||||
"path": "m/44'/0'/0'/0/0",
|
||||
"address_type": "legacy",
|
||||
"address": "1PEha8dk5Me5J1rZWpgqSt5F4BroTBLS5y"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user