Menu
Nostr Python Executable Jan 31, 2026

Generate Nostr Keypairs

Create Nostr identity keypairs using secp256k1 cryptography

#keys #identity #secp256k1 #bech32

Overview

Generate Nostr identity keypairs. Your private key (nsec) is your identity - guard it carefully. Your public key (npub) is shareable and identifies you on the network.

The Code

"""
Nostr Keypair Generator
Create identity keypairs using secp256k1

Requirements: None (uses hashlib for CSPRNG, includes bech32 encoder)

SECURITY: Private keys grant full control of your Nostr identity.
Never share, log, or expose nsec keys.
"""

import hashlib
import secrets
from typing import Tuple

# Bech32 charset for NIP-19 encoding
BECH32_CHARSET = "qpzry9x8gf2tvdw0s3jn54khce6mua7l"

# secp256k1 curve parameters
P = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F
N = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141
G = (
    0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798,
    0x483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8
)


def modinv(a: int, m: int) -> int:
    """Modular multiplicative inverse using extended Euclidean algorithm."""
    if a < 0:
        a = a % m
    g, x, _ = extended_gcd(a, m)
    if g != 1:
        raise ValueError("Modular inverse does not exist")
    return x % m


def extended_gcd(a: int, b: int) -> Tuple[int, int, int]:
    """Extended Euclidean algorithm."""
    if a == 0:
        return b, 0, 1
    g, x, y = extended_gcd(b % a, a)
    return g, y - (b // a) * x, x


def point_add(p1: Tuple[int, int], p2: Tuple[int, int]) -> Tuple[int, int]:
    """Add two points on secp256k1."""
    if p1 is None:
        return p2
    if p2 is None:
        return p1

    x1, y1 = p1
    x2, y2 = p2

    if x1 == x2 and y1 != y2:
        return None

    if x1 == x2:
        m = (3 * x1 * x1) * modinv(2 * y1, P) % P
    else:
        m = (y2 - y1) * modinv(x2 - x1, P) % P

    x3 = (m * m - x1 - x2) % P
    y3 = (m * (x1 - x3) - y1) % P
    return (x3, y3)


def scalar_mult(k: int, point: Tuple[int, int]) -> Tuple[int, int]:
    """Multiply a point by a scalar."""
    result = None
    addend = point

    while k:
        if k & 1:
            result = point_add(result, addend)
        addend = point_add(addend, addend)
        k >>= 1

    return result


def generate_private_key() -> bytes:
    """
    Generate a cryptographically secure private key.

    Returns:
        32 bytes private key
    """
    while True:
        key = secrets.token_bytes(32)
        key_int = int.from_bytes(key, 'big')
        # Ensure key is valid for secp256k1
        if 0 < key_int < N:
            return key


def private_to_public(private_key: bytes) -> bytes:
    """
    Derive public key from private key.

    Args:
        private_key: 32 bytes private key

    Returns:
        32 bytes public key (x-coordinate only, for Nostr)
    """
    k = int.from_bytes(private_key, 'big')
    point = scalar_mult(k, G)
    return point[0].to_bytes(32, 'big')


def bech32_polymod(values: list) -> int:
    """Bech32 checksum computation."""
    generator = [0x3b6a57b2, 0x26508e6d, 0x1ea119fa, 0x3d4233dd, 0x2a1462b3]
    chk = 1
    for value in values:
        top = chk >> 25
        chk = (chk & 0x1ffffff) << 5 ^ value
        for i in range(5):
            chk ^= generator[i] if ((top >> i) & 1) else 0
    return chk


def bech32_hrp_expand(hrp: str) -> list:
    """Expand HRP for checksum."""
    return [ord(x) >> 5 for x in hrp] + [0] + [ord(x) & 31 for x in hrp]


def bech32_create_checksum(hrp: str, data: list) -> list:
    """Create bech32 checksum."""
    values = bech32_hrp_expand(hrp) + data
    polymod = bech32_polymod(values + [0, 0, 0, 0, 0, 0]) ^ 1
    return [(polymod >> 5 * (5 - i)) & 31 for i in range(6)]


def convertbits(data: bytes, frombits: int, tobits: int, pad: bool = True) -> list:
    """Convert between bit sizes."""
    acc = 0
    bits = 0
    ret = []
    maxv = (1 << tobits) - 1

    for value in data:
        acc = (acc << frombits) | value
        bits += frombits
        while bits >= tobits:
            bits -= tobits
            ret.append((acc >> bits) & maxv)

    if pad and bits:
        ret.append((acc << (tobits - bits)) & maxv)

    return ret


def bytes_to_bech32(hrp: str, data: bytes) -> str:
    """
    Encode bytes to bech32 string.

    Args:
        hrp: Human-readable prefix ('nsec' or 'npub')
        data: 32 bytes to encode

    Returns:
        bech32-encoded string
    """
    data5bit = convertbits(data, 8, 5)
    checksum = bech32_create_checksum(hrp, data5bit)
    return hrp + '1' + ''.join(BECH32_CHARSET[d] for d in data5bit + checksum)


def generate_keypair() -> dict:
    """
    Generate a complete Nostr keypair.

    Returns:
        dict with private key, public key, nsec, and npub
    """
    private_key = generate_private_key()
    public_key = private_to_public(private_key)

    return {
        "private_key_hex": private_key.hex(),
        "public_key_hex": public_key.hex(),
        "nsec": bytes_to_bech32("nsec", private_key),
        "npub": bytes_to_bech32("npub", public_key)
    }


def validate_keypair(nsec: str, npub: str) -> bool:
    """
    Validate that nsec and npub form a valid keypair.

    Returns:
        True if valid keypair
    """
    # This would require bech32 decoding
    # Simplified: just check format
    return (
        nsec.startswith("nsec1") and
        npub.startswith("npub1") and
        len(nsec) == 63 and
        len(npub) == 63
    )


# Example usage
if __name__ == "__main__":
    print("=== Nostr Keypair Generator ===\n")

    keypair = generate_keypair()

    print("PUBLIC KEY (shareable):")
    print(f"  npub: {keypair['npub']}")
    print(f"  hex:  {keypair['public_key_hex']}")

    print("\nPRIVATE KEY (SECRET - DO NOT SHARE):")
    print(f"  nsec: {keypair['nsec']}")
    print(f"  hex:  {keypair['private_key_hex']}")

    print("\n" + "=" * 50)
    print("SECURITY WARNINGS:")
    print("1. NEVER share your nsec (private key)")
    print("2. NEVER paste nsec into websites")
    print("3. Back up nsec securely (password manager)")
    print("4. Anyone with nsec can impersonate you")
    print("=" * 50)

    # Validate the keypair
    is_valid = validate_keypair(keypair['nsec'], keypair['npub'])
    print(f"\nKeypair valid: {is_valid}")

Usage

# No external dependencies required
python generate_keys.py

Example Output

=== Nostr Keypair Generator ===

PUBLIC KEY (shareable):
  npub: npub1qy6nkwjz69...
  hex:  012a5b38298f...

PRIVATE KEY (SECRET - DO NOT SHARE):
  nsec: nsec1xyz789abc...
  hex:  3456def789...

==================================================
SECURITY WARNINGS:
1. NEVER share your nsec (private key)
2. NEVER paste nsec into websites
3. Back up nsec securely (password manager)
4. Anyone with nsec can impersonate you
==================================================

Keypair valid: True

Agent Notes

Key formats:

  • npub1...: Public key in bech32 (NIP-19)
  • nsec1...: Private key in bech32 (NIP-19)
  • Hex: Raw 32-byte keys as hexadecimal

Security for agents:

  1. Store nsec in secure environment variables
  2. Never log private keys
  3. Use separate keys for different agent identities
  4. Consider key rotation for high-risk operations

For production: Use established libraries like pynostr or nostr-sdk which have been audited.