Compare commits

...

10 Commits

Author SHA1 Message Date
f9a69e4992 Update nostr-keygen 2025-10-09 06:40:48 -04:00
Logen
ae34fb5762 Update README.md 2025-10-02 14:17:55 -04:00
Logen
937a9c8ba1 1.2.1 2025-10-02 14:17:05 -04:00
28532bd6d3 1.2.0 2025-10-02 14:14:21 -04:00
42b1a26f8d readme 2025-09-29 05:14:34 -04:00
Logen
3b32ca76b3 0.1.1 2025-09-08 20:00:29 -04:00
Logen
6ac9ae4566 0.1.0 2025-09-08 19:58:43 -04:00
Logen
652222a0a6 final cleanup 2025-09-08 19:55:49 -04:00
Logen
06408a3bfa git ignore 2025-09-08 19:52:30 -04:00
Logen
b595c91d19 gitignore 2025-09-08 19:48:35 -04:00
12 changed files with 232 additions and 414 deletions

4
.gitignore vendored
View File

@@ -1,4 +1,6 @@
nostr_keygen.egg-info
build/
nostr_keygen.egg-info/
__pycache__/
venv/
*.pyc

181
README.md
View File

@@ -1,115 +1,96 @@
# nostr-keygen
# NostrKeyGen
A tiny commandline utility written in Python that generates an Nostr **npub** (public key) and **nsec** (private key) pair from a single file that you drop into the terminal.
A tiny, interactive commandline tool that lets you generate a Nostr key pair from any file you choose. It works on macOS, Linux and (with a few tweaks) Windows. The script is intentionally lightweight no fancy GUI, just an easytofollow wizard run from your terminal.
> **NOTE:** The instructions below work on **macOS** and **Linux**. If youre on Windows youll need a compatible terminal (e.g. WSL, GitBash, or PowerShell with Python).
## Features
---
- **Draganddrop friendly**: on macOS and Linux you can simply drop the file into the Terminal window.
- **Clear, stepbystep tutorial**: every run prints a short intro, an explanation, a quick demo, asks for your file and finishes with the keypair in *both* NostrBech32 and raw hex.
- **No external binaries** it just uses the purePython `ecdsa` and `bech32` packages.
- **Portable**: you can ship it as a single Python file or compile to a binary with tools such as PyInstaller.
## 🚀 Run
## Install
### 1. Clone the repo
```bash
# Drop any file into the terminal prompt!
# macOS (most terminals) and many Linux terminals allow draganddrop.
nostr-keygen <filepath>
```
> The terminal prompt you see (``$`` on macOS, ``username@host:~$`` on Linux) accepts draganddrop of any file. The script will read that file path and produce key strings.
---
### 📄 Quick test
```bash
# Create a tiny dummy file
printf "random entropy" > /tmp/dummy.bin
# Run the program
nostr-keygen /tmp/dummy.bin
```
You should see two lines outputted: an `nsec` string and an `npub` string.
## 🛠️ Installation
### 1⃣ Install Python
- **macOS**: Use Homebrew
```bash
brew install python@3.12
```
(If you already have Python, skip this step.)
- **Linux (Ubuntu / Debianbased)**:
```bash
sudo apt-get update
sudo apt-get install python3 python3-venv
```
(Other distros may use `yum`, `dnf`, or your package manager of choice.)
- **Linux (Arch)**:
```bash
sudo pacman -S python
```
> **Tip** On macOS the `python3` binary is typically symlinked to `python`, but on many Linux systems youll need `python3` explicitly.
### 2⃣ Clone the repo
```bash
git clone https://github.com/yourgithubhandle/nostr-keygen.git
git clone https://github.com/btcforplebs/nostr-keygen.git
cd nostr-keygen
```
### 3 Create a virtualenvironment (recommended)
### 2. Create and activate a virtual environment
```bash
python3 -m venv .venv # create a venv in the repo directory
source .venv/bin/activate # activate it (both on macOS and Linux)
python3 -m venv venv
source venv/bin/activate
```
> The virtualenvironment isolates thirdparty libraries (`ecdsa`, `bech32`, etc.) from the systemwide Python installation, ensuring that installing or updating them wont accidentally break other projects. It also guarantees that anyone who checks out the repo can recreate the exact same runtime environment.
### 4⃣ Install the tool in editable mode (development) or normally
- **Editable (work on the code as you edit it)**
```bash
pip install -e .
```
- **Normal installation**
```bash
pip install .
```
Both forms install the consolescript `nostrkeygen` into `./.venv/bin`.
## 🚫 Keystring safety
> Keep your `nsec` secret in a secure, offline location. Anyone with that string can sign Nostr events or spend Nostrbased funds.
## 🔧 How the code works (quick dive)
```python
# main.py
import argparse, hashlib
from ecdsa import SigningKey, SECP256k1
from bech32 import bech32_encode, convertbits
NSEC_PREFIX, NPUB_PREFIX = "nsec", "npub"
def _to_bech32(data: bytes, hrp: str) -> str:
five_bits = convertbits(list(data), 8, 5, True)
return bech32_encode(hrp, five_bits)
# … (rest unchanged) …
### 3. Install dependencies
```bash
pip install ecdsa bech32
```
### Why the keygen algorithm matters
### 4. Make it executable and optional symlink
```bash
chmod +x nostr-keygen
```
## Usage
Run the script directly from the project directory:
```bash
./nostr-keygen
```
Youll be guided through the following steps:
1. Welcome screen press **Enter** to continue.
2. Tool overview press **Enter** to continue.
3. Demo key generation using a temporary file press **Enter** to continue.
4. Prompt for the path to your entropy file. You can drop a file into the terminal on macOS/Linux or type the absolute/relative path.
5. The script shows you the generated `nsec`, `npub`, and the hex representation of both keys.
6. Press **Enter** again, the terminal is cleared and the program exits.
## Example Output
```
🛠️ Welcome to NostrKeyGen
This tool will:
- Pick any file as your entropy source
- Hash the file → 32byte seed
- Derive a secp256k1 key pair
- Print the Nostr `nsec`/`npub` and the raw hex values
💡 Demo (using a random file in /tmp)...
Demo nsec: nsec1q4...
Demo npub: npub1p8...
Demo priv hex: 1f4c...
Demo pub hex: 03a1...
📂 Enter the path to your entropy file: /Users/me/secret.txt
✅ Your new key pair:
- nsec : nsec1q8...
- npub : npub1p9...
- priv hex : 7c2d...
- pub hex : 029b...
🧹 Cleaning up...
🚀 Done. Goodbye!
```
## FAQs
| Question | Answer |
|----------|--------|
| Why does it read *any* file as entropy? | Nostr requires a 32byte seed. Hashing a file guarantees a deterministic, pseudorandom 32byte output irrespective of the file size or content. |
| Will it always produce the same key for the same file? | Yes the seed is derived from a SHA256 hash of the file contents. |
| Can I use a password instead? | The current script accepts files only, but you could easily pipe a password string using `echo -n 'mypassword' > pwd.txt` first. |
## Contributing
Pull requests are welcome! Just make sure the code passes `flake8`/`black` and contains an updated README.
## License
MIT see [LICENSE](LICENSE).
1. **Read file in binary** We need raw entropy; reading as text would truncate or encode the file in an unexpected way. Binary mode is the most faithful representation of the files content.
2. **SHA256 hash** Provides a *deterministic* 32byte seed from any file. Different inputs yield different seeds, and the same input always yields the same seed.
3. **Create a secp256k1 signing key** The curve used by Nostr (and Bitcoin) for ECDSA. The secret key is derived directly from the seed bytes.
4. **Bech32 encoding** Nostr keys are humanreadable Bech32 strings prefixed with `nsec` or `npub`. `_to_bech32` converts 8bit byte streams to 5bit groups and encodes them.

View File

@@ -1,79 +0,0 @@
#!/usr/bin/env python3
"""Generate a fresh Nostr key (npub/nsec) using a file as entropy source.
Usage:
nostr-keygen <path-to-file>
The file is read in binary mode and its bytes are hashed with SHA256 to
produce the 32byte seed which is then used to generate a secp256k1
private key. The private key is hexencoded as an nsec, and the
corresponding public key is converted to a Bech32 npub. The example
uses the official Nostr prefix "nsec"/
"""
import argparse
import hashlib
import os
import sys
from ecdsa import SigningKey, SECP256k1
from bech32 import bech32_encode, convertbits
# Constants for Nostr bech32 encoding
NSEC_PREFIX = "nsec"
NPUB_PREFIX = "npub"
# convertbits helper adapted from bech32 library; using provided function for clarity
def _to_bech32(data: bytes, hrp: str) -> str:
"""Encode raw bytes into a Bech32 string with the given humanreadable part."""
# Convert 8bit bytes to 5bit groups
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 an ECDSA SECP256k1 private key derived from entropy."""
# Use SHA256 of the provided entropy for deterministic key generation
seed = hashlib.sha256(entropy).digest()
return SigningKey.from_string(seed, curve=SECP256k1)
def generate_key_from_file(file_path: str) -> (str, str):
"""Return (nsec, npub) for the key derived from the file content."""
if not os.path.isfile(file_path):
raise FileNotFoundError(f"File not found: {file_path}")
with open(file_path, "rb") as f:
data = f.read()
sk = _entropy_to_pri_key(data)
vk = sk.get_verifying_key()
# Private key bytes
private_bytes = sk.to_string()
# Public key bytes, compressed (33 bytes)
public_bytes = vk.to_string("compressed")
nsec = _to_bech32(private_bytes, NSEC_PREFIX)
npub = _to_bech32(public_bytes, NPUB_PREFIX)
return nsec, npub
def main():
parser = argparse.ArgumentParser(description="Generate Nostr key pair from file entropy")
parser.add_argument("file", help="Path to file used as entropy source")
args = parser.parse_args()
try:
nsec, npub = generate_key_from_file(args.file)
except Exception as e:
print(f"Error: {e}", file=sys.stderr)
sys.exit(1)
print("NSec:", nsec)
print("NPub:", npub)
if __name__ == "__main__":
main()

79
main.py
View File

@@ -1,79 +0,0 @@
#!/usr/bin/env python3
"""Generate a fresh Nostr key (npub/nsec) using a file as entropy source.
Usage:
nostr-keygen <path-to-file>
The file is read in binary mode and its bytes are hashed with SHA256 to
produce the 32byte seed which is then used to generate a secp256k1
private key. The private key is hexencoded as an nsec, and the
corresponding public key is converted to a Bech32 npub. The example
uses the official Nostr prefix "nsec"/
"""
import argparse
import hashlib
import os
import sys
from ecdsa import SigningKey, SECP256k1
from bech32 import bech32_encode, convertbits
# Constants for Nostr bech32 encoding
NSEC_PREFIX = "nsec"
NPUB_PREFIX = "npub"
# convertbits helper adapted from bech32 library; using provided function for clarity
def _to_bech32(data: bytes, hrp: str) -> str:
"""Encode raw bytes into a Bech32 string with the given humanreadable part."""
# Convert 8bit bytes to 5bit groups
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 an ECDSA SECP256k1 private key derived from entropy."""
# Use SHA256 of the provided entropy for deterministic key generation
seed = hashlib.sha256(entropy).digest()
return SigningKey.from_string(seed, curve=SECP256k1)
def generate_key_from_file(file_path: str) -> (str, str):
"""Return (nsec, npub) for the key derived from the file content."""
if not os.path.isfile(file_path):
raise FileNotFoundError(f"File not found: {file_path}")
with open(file_path, "rb") as f:
data = f.read()
sk = _entropy_to_pri_key(data)
vk = sk.get_verifying_key()
# Private key bytes
private_bytes = sk.to_string()
# Public key bytes, compressed (33 bytes)
public_bytes = vk.to_string("compressed")
nsec = _to_bech32(private_bytes, NSEC_PREFIX)
npub = _to_bech32(public_bytes, NPUB_PREFIX)
return nsec, npub
def main():
parser = argparse.ArgumentParser(description="Generate Nostr key pair from file entropy")
parser.add_argument("file", help="Path to file used as entropy source")
args = parser.parse_args()
try:
nsec, npub = generate_key_from_file(args.file)
except Exception as e:
print(f"Error: {e}", file=sys.stderr)
sys.exit(1)
print("NSec:", nsec)
print("NPub:", npub)
if __name__ == "__main__":
main()

148
nostr-keygen Executable file
View File

@@ -0,0 +1,148 @@
#!/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 'python3 -m venv venv && source venv/bin/activate && 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):
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 safe 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()

View File

@@ -1,125 +0,0 @@
Metadata-Version: 2.4
Name: nostr-keygen
Version: 0.1.0
Summary: Terminal tool to generate nostr npub/nsec from file entropy
Author-email: Your Name <you@example.com>
Requires-Python: >=3.8
Description-Content-Type: text/markdown
Requires-Dist: ecdsa
Requires-Dist: bech32
# nostr-keygen
A tiny commandline utility written in Python that generates an Nostr **npub** (public key) and **nsec** (private key) pair from a single file that you drop into the terminal.
> **NOTE:** The instructions below work on **macOS** and **Linux**. If youre on Windows youll need a compatible terminal (e.g. WSL, GitBash, or PowerShell with Python).
---
## 🚀 Run
```bash
# Drop any file into the terminal prompt!
# macOS (most terminals) and many Linux terminals allow draganddrop.
nostr-keygen <filepath>
```
> The terminal prompt you see (``$`` on macOS, ``username@host:~$`` on Linux) accepts draganddrop of any file. The script will read that file path and produce key strings.
---
### 📄 Quick test
```bash
# Create a tiny dummy file
printf "random entropy" > /tmp/dummy.bin
# Run the program
nostr-keygen /tmp/dummy.bin
```
You should see two lines outputted: an `nsec` string and an `npub` string.
## 🛠️ Installation
### 1⃣ Install Python
- **macOS**: Use Homebrew
```bash
brew install python@3.12
```
(If you already have Python, skip this step.)
- **Linux (Ubuntu / Debianbased)**:
```bash
sudo apt-get update
sudo apt-get install python3 python3-venv
```
(Other distros may use `yum`, `dnf`, or your package manager of choice.)
- **Linux (Arch)**:
```bash
sudo pacman -S python
```
> **Tip** On macOS the `python3` binary is typically symlinked to `python`, but on many Linux systems youll need `python3` explicitly.
### 2⃣ Clone the repo
```bash
git clone https://github.com/yourgithubhandle/nostr-keygen.git
cd nostr-keygen
```
### 3⃣ Create a virtualenvironment (recommended)
```bash
python3 -m venv .venv # create a venv in the repo directory
source .venv/bin/activate # activate it (both on macOS and Linux)
```
> The virtualenvironment isolates thirdparty libraries (`ecdsa`, `bech32`, etc.) from the systemwide Python installation, ensuring that installing or updating them wont accidentally break other projects. It also guarantees that anyone who checks out the repo can recreate the exact same runtime environment.
### 4⃣ Install the tool in editable mode (development) or normally
- **Editable (work on the code as you edit it)**
```bash
pip install -e .
```
- **Normal installation**
```bash
pip install .
```
Both forms install the consolescript `nostrkeygen` into `./.venv/bin`.
## 🚫 Keystring safety
> Keep your `nsec` secret in a secure, offline location. Anyone with that string can sign Nostr events or spend Nostrbased funds.
## 🔧 How the code works (quick dive)
```python
# main.py
import argparse, hashlib
from ecdsa import SigningKey, SECP256k1
from bech32 import bech32_encode, convertbits
NSEC_PREFIX, NPUB_PREFIX = "nsec", "npub"
def _to_bech32(data: bytes, hrp: str) -> str:
five_bits = convertbits(list(data), 8, 5, True)
return bech32_encode(hrp, five_bits)
# … (rest unchanged) …
```
### Why the keygen algorithm matters
1. **Read file in binary** We need raw entropy; reading as text would truncate or encode the file in an unexpected way. Binary mode is the most faithful representation of the files content.
2. **SHA256 hash** Provides a *deterministic* 32byte seed from any file. Different inputs yield different seeds, and the same input always yields the same seed.
3. **Create a secp256k1 signing key** The curve used by Nostr (and Bitcoin) for ECDSA. The secret key is derived directly from the seed bytes.
4. **Bech32 encoding** Nostr keys are humanreadable Bech32 strings prefixed with `nsec` or `npub`. `_to_bech32` converts 8bit byte streams to 5bit groups and encodes them.

View File

@@ -1,9 +0,0 @@
README.md
main.py
pyproject.toml
nostr_keygen.egg-info/PKG-INFO
nostr_keygen.egg-info/SOURCES.txt
nostr_keygen.egg-info/dependency_links.txt
nostr_keygen.egg-info/entry_points.txt
nostr_keygen.egg-info/requires.txt
nostr_keygen.egg-info/top_level.txt

View File

@@ -1 +0,0 @@

View File

@@ -1,2 +0,0 @@
[console_scripts]
nostr-keygen = main:main

View File

@@ -1,2 +0,0 @@
ecdsa
bech32

View File

@@ -1 +0,0 @@
main

View File

@@ -1,15 +0,0 @@
[build-system]
requires = ["setuptools>=61.0", "wheel"]
build-backend = "setuptools.build_meta"
[project]
name = "nostr-keygen"
version = "0.1.0"
description = "Terminal tool to generate nostr npub/nsec from file entropy"
authors = [{name = "Your Name", email = "you@example.com"}]
readme = "README.md"
requires-python = ">=3.8"
dependencies = ["ecdsa", "bech32"]
[project.scripts]
nostr-keygen = "main:main"