Skip to content

Instantly share code, notes, and snippets.

@ferib
Created October 6, 2025 11:33
Show Gist options
  • Select an option

  • Save ferib/71cfeb41ce6cc82ae28c520e8e27d18d to your computer and use it in GitHub Desktop.

Select an option

Save ferib/71cfeb41ce6cc82ae28c520e8e27d18d to your computer and use it in GitHub Desktop.
Converts pubkey to Nostr address
#!/usr/bin/env python3
"""
% python3 npub1.py 0350dbc4dfd2313112fa6d1ac3effed7d1428b3c8dcc7357c0b84bac8f356a1894
Compressed BTC pubkey (hex): 0350dbc4dfd2313112fa6d1ac3effed7d1428b3c8dcc7357c0b84bac8f356a1894
Nostr x-only pubkey (hex) : 50dbc4dfd2313112fa6d1ac3effed7d1428b3c8dcc7357c0b84bac8f356a1894
Nostr npub : npub12rdufh7jxyc397ndrtp7llkh69pgk0yde3e40s9cfwkg7dt2rz2qjdzd2t
"""
import argparse
import binascii
import sys
# ---------- Bech32 (BIP-173) for NIP-19 ----------
CHARSET = "qpzry9x8gf2tvdw0s3jn54khce6mua7l"
def bech32_polymod(values):
GENERATORS = [0x3b6a57b2, 0x26508e6d, 0x1ea119fa, 0x3d4233dd, 0x2a1462b3]
chk = 1
for v in values:
b = chk >> 25
chk = ((chk & 0x1ffffff) << 5) ^ v
for i in range(5):
if (b >> i) & 1:
chk ^= GENERATORS[i]
return chk
def bech32_hrp_expand(hrp):
return [ord(x) >> 5 for x in hrp] + [0] + [ord(x) & 31 for x in hrp]
def bech32_create_checksum(hrp, data):
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 bech32_encode(hrp, data):
combined = data + bech32_create_checksum(hrp, data)
return hrp + '1' + ''.join([CHARSET[d] for d in combined])
def convertbits(data, frombits, tobits, pad=True):
acc = 0
bits = 0
ret = []
maxv = (1 << tobits) - 1
max_acc = (1 << (frombits + tobits - 1)) - 1
for value in data:
if value < 0 or (value >> frombits):
return None
acc = ((acc << frombits) | value) & max_acc
bits += frombits
while bits >= tobits:
bits -= tobits
ret.append((acc >> bits) & maxv)
if pad:
if bits:
ret.append((acc << (tobits - bits)) & maxv)
elif bits >= frombits or ((acc << (tobits - bits)) & maxv):
return None
return ret
def nip19_encode_npub(pubkey_bytes: bytes) -> str:
"""Encode raw 32-byte pubkey into NIP-19 'npub' (Bech32, not Bech32m)."""
data5 = convertbits(list(pubkey_bytes), 8, 5, True)
if data5 is None:
raise ValueError("convertbits failed")
return bech32_encode("npub", data5)
def is_compressed_pubkey(pub: bytes) -> bool:
return len(pub) == 33 and pub[0] in (0x02, 0x03)
def main():
ap = argparse.ArgumentParser(
description="Convert a compressed Bitcoin pubkey (02/03 + 32 bytes) into a Nostr pubkey (hex x-only and npub)."
)
ap.add_argument("pubkey_hex", help="Compressed secp256k1 pubkey in hex (starts with 02/03, 33 bytes).")
args = ap.parse_args()
try:
pub = bytes.fromhex(args.pubkey_hex.strip())
except ValueError:
print("Error: invalid hex for pubkey.", file=sys.stderr)
sys.exit(1)
if not is_compressed_pubkey(pub):
print("Error: pubkey must be 33 bytes and start with 0x02 or 0x03 (compressed).", file=sys.stderr)
sys.exit(1)
# Extract x-only (drop the first byte)
x_only = pub[1:]
x_only_hex = x_only.hex()
npub = nip19_encode_npub(x_only)
print("Compressed BTC pubkey (hex):", args.pubkey_hex.lower())
print("Nostr x-only pubkey (hex) :", x_only_hex)
print("Nostr npub :", npub)
if __name__ == "__main__":
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment