103 lines
3.1 KiB
Python
103 lines
3.1 KiB
Python
|
|
|
||
|
|
import hashlib
|
||
|
|
import hmac
|
||
|
|
import os
|
||
|
|
import binascii
|
||
|
|
|
||
|
|
# ═══════════════════════════════════════════
|
||
|
|
# NOSTR SOVEREIGN IDENTITY (NIP-01)
|
||
|
|
# ═══════════════════════════════════════════
|
||
|
|
# Pure Python implementation of Schnorr signatures for Nostr.
|
||
|
|
# No dependencies required.
|
||
|
|
|
||
|
|
def sha256(data):
|
||
|
|
return hashlib.sha256(data).digest()
|
||
|
|
|
||
|
|
def hmac_sha256(key, data):
|
||
|
|
return hmac.new(key, data, hashlib.sha256).digest()
|
||
|
|
|
||
|
|
# Secp256k1 Constants
|
||
|
|
P = 2**256 - 2**32 - 977
|
||
|
|
N = 115792089237316195423570985008687907852837564279074904382605163141518161494337
|
||
|
|
G = (0x79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798,
|
||
|
|
0x483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8)
|
||
|
|
|
||
|
|
def inverse(a, n):
|
||
|
|
return pow(a, n - 2, n)
|
||
|
|
|
||
|
|
def point_add(p1, p2):
|
||
|
|
if p1 is None: return p2
|
||
|
|
if p2 is None: return p1
|
||
|
|
(x1, y1), (x2, y2) = p1, p2
|
||
|
|
if x1 == x2 and y1 != y2: return None
|
||
|
|
if x1 == x2:
|
||
|
|
m = (3 * x1 * x1 * inverse(2 * y1, P)) % P
|
||
|
|
else:
|
||
|
|
m = ((y2 - y1) * inverse(x2 - x1, P)) % P
|
||
|
|
x3 = (m * m - x1 - x2) % P
|
||
|
|
y3 = (m * (x1 - x3) - y1) % P
|
||
|
|
return (x3, y3)
|
||
|
|
|
||
|
|
def point_mul(p, n):
|
||
|
|
r = None
|
||
|
|
for i in range(256):
|
||
|
|
if (n >> i) & 1:
|
||
|
|
r = point_add(r, p)
|
||
|
|
p = point_add(p, p)
|
||
|
|
return r
|
||
|
|
|
||
|
|
def get_pubkey(privkey):
|
||
|
|
p = point_mul(G, privkey)
|
||
|
|
return binascii.hexlify(p[0].to_bytes(32, 'big')).decode()
|
||
|
|
|
||
|
|
# Schnorr Signature (BIP340)
|
||
|
|
def sign_schnorr(msg_hash, privkey):
|
||
|
|
k = int.from_bytes(sha256(privkey.to_bytes(32, 'big') + msg_hash), 'big') % N
|
||
|
|
R = point_mul(G, k)
|
||
|
|
if R[1] % 2 != 0:
|
||
|
|
k = N - k
|
||
|
|
r = R[0].to_bytes(32, 'big')
|
||
|
|
e = int.from_bytes(sha256(r + binascii.unhexlify(get_pubkey(privkey)) + msg_hash), 'big') % N
|
||
|
|
s = (k + e * privkey) % N
|
||
|
|
return binascii.hexlify(r + s.to_bytes(32, 'big')).decode()
|
||
|
|
|
||
|
|
class NostrIdentity:
|
||
|
|
def __init__(self, privkey_hex=None):
|
||
|
|
if privkey_hex:
|
||
|
|
self.privkey = int(privkey_hex, 16)
|
||
|
|
else:
|
||
|
|
self.privkey = int.from_bytes(os.urandom(32), 'big') % N
|
||
|
|
self.pubkey = get_pubkey(self.privkey)
|
||
|
|
|
||
|
|
def sign_event(self, event):
|
||
|
|
# NIP-01 Event Signing
|
||
|
|
import json
|
||
|
|
event_data = [
|
||
|
|
0,
|
||
|
|
event['pubkey'],
|
||
|
|
event['created_at'],
|
||
|
|
event['kind'],
|
||
|
|
event['tags'],
|
||
|
|
event['content']
|
||
|
|
]
|
||
|
|
serialized = json.dumps(event_data, separators=(',', ':'))
|
||
|
|
msg_hash = sha256(serialized.encode())
|
||
|
|
event['id'] = binascii.hexlify(msg_hash).decode()
|
||
|
|
event['sig'] = sign_schnorr(msg_hash, self.privkey)
|
||
|
|
return event
|
||
|
|
|
||
|
|
if __name__ == "__main__":
|
||
|
|
# Test Identity
|
||
|
|
identity = NostrIdentity()
|
||
|
|
print(f"Nostr Pubkey: {identity.pubkey}")
|
||
|
|
|
||
|
|
event = {
|
||
|
|
"pubkey": identity.pubkey,
|
||
|
|
"created_at": 1677628800,
|
||
|
|
"kind": 1,
|
||
|
|
"tags": [],
|
||
|
|
"content": "Sovereignty and service always. #Timmy"
|
||
|
|
}
|
||
|
|
signed_event = identity.sign_event(event)
|
||
|
|
print(f"Signed Event: {signed_event}")
|