Files
nostr-keygen/nostr-keygen
2025-10-02 14:14:21 -04:00

149 lines
4.5 KiB
Python
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
"""A friendly CLI that walks you through generating a Nostr key pair
from a file you pick on the fly.
Run it with:
./nostr-keygen
"""
import time
import argparse
import hashlib
import os
import sys
from pathlib import Path
# ----- IMPORTS ---------------------------------------------------------------
try:
from ecdsa import SigningKey, SECP256k1
from bech32 import bech32_encode, convertbits
except ImportError as e:
print("⚠️ Required packages are missing.\nRun 'pip install ecdsa bech32' first.", file=sys.stderr)
sys.exit(1)
# ----- CONSTANTS -------------------------------------------------------------
NSEC_PREFIX = "nsec"
NPUB_PREFIX = "npub"
# ----- HELPER FUNCTIONS ------------------------------------------------------
def _to_bech32(data: bytes, hrp: str) -> str:
"""Encode raw bytes into a Bech32 string with the given humanreadable part."""
five_bits = convertbits(list(data), 8, 5, True)
if five_bits is None:
raise ValueError("Error converting data to 5bit groups")
return bech32_encode(hrp, five_bits)
def _entropy_to_pri_key(entropy: bytes) -> SigningKey:
"""Return a SECP256k1 private key derived from SHA256 of the entropy."""
seed = hashlib.sha256(entropy).digest()
return SigningKey.from_string(seed, curve=SECP256k1)
def generate_key_from_file(file_path: Path):
"""Return (nsec, npub, private_hex, public_hex)."""
if not file_path.is_file():
raise FileNotFoundError(f"File not found: {file_path}")
with file_path.open("rb") as f:
data = f.read()
sk = _entropy_to_pri_key(data)
vk = sk.get_verifying_key()
private_bytes = sk.to_string()
public_bytes = vk.to_string("compressed")
nsec = _to_bech32(private_bytes, NSEC_PREFIX)
npub = _to_bech32(public_bytes, NPUB_PREFIX)
return nsec, npub, private_bytes.hex(), public_bytes.hex()
# ----- CLI / UI ---------------------------------------------------------------
def welcome():
clear_terminal()
print("\n" + "="*30)
print("🛠️ Welcome to NostrKeyGen")
print("="*30 + "\n")
def info():
clear_terminal()
print("This tool will help you:")
print("- Pick any file as your entropy source")
print("- Hash the file → 32byte seed")
print("- Derive a secp256k1 key pair")
print("- Print the Nostr `nsec`/`npub` and the raw hex values")
print()
def demo():
clear_terminal()
print("💡 EXAMPLE (using a random file in /tmp)...")
example = Path("/tmp/nostr_demo_entropy.txt")
example.write_bytes(os.urandom(256))
try:
nsec, npub, priv_hex, pub_hex = generate_key_from_file(example)
print(f" DEMO nsec: {nsec}")
print(f" DEMO npub: {npub}")
print(f" DEMO priv hex: {priv_hex}")
print(f" DEMO pub hex: {pub_hex}")
except Exception as e:
print(f" Cannot generate demo key: {e}")
finally:
example.unlink(missing_ok=True)
print()
def ask_file() -> Path:
clear_terminal()
while True:
path = input("📂 Enter the path or drop your file: ").strip()
if not path:
print(" ✨ Nothing entered, try again.")
continue
p = Path(path).expanduser().resolve()
if p.is_file():
return p
print(" ❌ That path isnt a file. Try again.")
def show_keys(nsec, npub, priv_hex, pub_hex):
clear_terminal()
print("\n✅ Your new key pair:")
print(f" - nsec : {nsec}")
print(f" - npub : {npub}")
print(f" - priv hex : {priv_hex}")
print(f" - pub hex : {pub_hex}\n")
def clear_terminal():
"""Clear the screen for a clean exit."""
os.system("clear" if os.name != "nt" else "cls")
def pause(prompt="[Enter] to continue"):
"""Wait for the user to hit Enter."""
input(f"\n{prompt}")
def main():
welcome()
pause("Press [Enter] to continue...")
info()
pause("Press [Enter] to continue...")
demo()
pause("Press [Enter] to continue...")
entropy_file = ask_file()
try:
nsec, npub, priv_hex, pub_hex = generate_key_from_file(entropy_file)
except Exception as e:
print(f"\n❌ Failed to generate keys: {e}", file=sys.stderr)
sys.exit(1)
show_keys(nsec, npub, priv_hex, pub_hex)
pause("Press [Enter] to clear the screen and exit...")
clear_terminal()
print("🚀 Done. Goodbye!\n")
sys.exit(0)
if __name__ == "__main__":
# If the script is run directly, invoke main()
main()