diff --git a/bot.py b/bot.py index 9d2c993..cfe34a8 100644 --- a/bot.py +++ b/bot.py @@ -234,7 +234,9 @@ class Bot: # else we create a zap else: - bolt11 = zap("ai@bitcoinfixesthis.org", amount, "Zap", ptag, etag, self.keys, self.dvm_config) + user = get_or_add_user(db=self.dvm_config.DB, npub=nostr_event.pubkey().to_hex(), + client=self.client, config=self.dvm_config) + bolt11 = zap(user.lud16, amount, "Zap", nostr_event, self.keys, self.dvm_config) if bolt11 == None: print("Receiver has no Lightning address") return @@ -242,7 +244,7 @@ class Bot: payment_hash = pay_bolt11_ln_bits(bolt11, self.dvm_config) self.job_list[self.job_list.index(entry)]['is_paid'] = True print("[" + self.NAME + "] payment_hash: " + payment_hash + - " Forwarding payment of " + amount + " Sats to DVM") + " Forwarding payment of " + str(amount) + " Sats to DVM") except Exception as e: print(e) diff --git a/dvm.py b/dvm.py index 4e5a528..395db6d 100644 --- a/dvm.py +++ b/dvm.py @@ -53,15 +53,15 @@ class DVM: self.client.connect() zap_filter = Filter().pubkey(pk).kinds([EventDefinitions.KIND_ZAP]).since(Timestamp.now()) - bot_dm_filter = Filter().pubkey(pk).kinds([EventDefinitions.KIND_DM]).authors(self.dvm_config.DM_ALLOWED).since( - Timestamp.now()) + #bot_dm_filter = Filter().pubkey(pk).kinds([EventDefinitions.KIND_DM]).authors(self.dvm_config.DM_ALLOWED).since( + # Timestamp.now()) kinds = [EventDefinitions.KIND_NIP90_GENERIC] for dvm in self.dvm_config.SUPPORTED_DVMS: if dvm.KIND not in kinds: kinds.append(dvm.KIND) dvm_filter = (Filter().kinds(kinds).since(Timestamp.now())) - self.client.subscribe([dvm_filter, zap_filter, bot_dm_filter]) + self.client.subscribe([dvm_filter, zap_filter]) create_sql_table(self.dvm_config.DB) admin_make_database_updates(adminconfig=self.admin_config, dvmconfig=self.dvm_config, client=self.client) @@ -158,7 +158,6 @@ class DVM: do_work(nip90_event) # if task is directed to us via p tag and user has balance, do the job and update balance elif p == Keys.from_sk_str(self.dvm_config.PRIVATE_KEY).public_key().to_hex() and user.balance >= amount: - balance = max(user.balance - amount, 0) update_sql_table(db=self.dvm_config.DB, npub=user.npub, balance=balance, iswhitelisted=user.iswhitelisted, isblacklisted=user.isblacklisted, @@ -261,14 +260,14 @@ class DVM: print("[" + self.dvm_config.NIP89.name + "] " "Someone zapped the result of an exisiting Task. Nice") elif not anon: - print("[" + self.dvm_config.NIP89.name + "] Note Zap received for Bot balance: " + + print("[" + self.dvm_config.NIP89.name + "] Note Zap received for DVM balance: " + str(invoice_amount) + " Sats from " + str(user.name)) update_user_balance(self.dvm_config.DB, sender, invoice_amount, client=self.client, config=self.dvm_config) # a regular note elif not anon: - print("[" + self.dvm_config.NIP89.name + "] Profile Zap received for Bot balance: " + + print("[" + self.dvm_config.NIP89.name + "] Profile Zap received for DVM balance: " + str(invoice_amount) + " Sats from " + str(user.name)) update_user_balance(self.dvm_config.DB, sender, invoice_amount, client=self.client, config=self.dvm_config) diff --git a/utils/zap_utils.py b/utils/zap_utils.py index 9a0185c..6389104 100644 --- a/utils/zap_utils.py +++ b/utils/zap_utils.py @@ -1,14 +1,17 @@ # LIGHTNING FUNCTIONS import json +import os import urllib.parse import requests from Crypto.Cipher import AES +from Crypto.Util.Padding import pad from bech32 import bech32_decode, convertbits, bech32_encode -from nostr_sdk import nostr_sdk, PublicKey, SecretKey, Event, EventBuilder, Tag +from nostr_sdk import nostr_sdk, PublicKey, SecretKey, Event, EventBuilder, Tag, Keys from utils.dvmconfig import DVMConfig from utils.nostr_utils import get_event_by_id import lnurl +from hashlib import sha256 def parse_amount_from_bolt11_invoice(bolt11_invoice: str) -> int: @@ -50,26 +53,26 @@ def parse_zap_event_tags(zap_event, keys, name, client, config): elif tag.as_vec()[0] == 'e': zapped_event = get_event_by_id(tag.as_vec()[1], client=client, config=config) elif tag.as_vec()[0] == 'description': - zap_request_event = Event.from_json(tag.as_vec()[1]) - sender = check_for_zapplepay(zap_request_event.pubkey().to_hex(), - zap_request_event.content()) - for z_tag in zap_request_event.tags(): - if z_tag.as_vec()[0] == 'anon': - if len(z_tag.as_vec()) > 1: - print("[" + name + "] Private Zap received.") - decrypted_content = decrypt_private_zap_message(z_tag.as_vec()[1], - keys.secret_key(), - zap_request_event.pubkey()) - decrypted_private_event = Event.from_json(decrypted_content) - if decrypted_private_event.kind() == 9733: - sender = decrypted_private_event.pubkey().to_hex() - message = decrypted_private_event.content() - if message != "": - print("Zap Message: " + message) - else: - anon = True - print( - "[" + name + "] Anonymous Zap received. Unlucky, I don't know from whom, and never will") + zap_request_event = Event.from_json(tag.as_vec()[1]) + sender = check_for_zapplepay(zap_request_event.pubkey().to_hex(), + zap_request_event.content()) + for z_tag in zap_request_event.tags(): + if z_tag.as_vec()[0] == 'anon': + if len(z_tag.as_vec()) > 1: + print("[" + name + "] Private Zap received.") + decrypted_content = decrypt_private_zap_message(z_tag.as_vec()[1], + keys.secret_key(), + zap_request_event.pubkey()) + decrypted_private_event = Event.from_json(decrypted_content) + if decrypted_private_event.kind() == 9733: + sender = decrypted_private_event.pubkey().to_hex() + message = decrypted_private_event.content() + if message != "": + print("Zap Message: " + message) + else: + anon = True + print( + "[" + name + "] Anonymous Zap received. Unlucky, I don't know from whom, and never will") return invoice_amount, zapped_event, sender, anon @@ -126,6 +129,26 @@ def check_for_zapplepay(pubkey_hex: str, content: str): return pubkey_hex +def enrypt_private_zap_message(message, privatekey, publickey): + # Generate a random IV + shared_secret = nostr_sdk.generate_shared_key(privatekey, publickey) + iv = os.urandom(16) + + # Encrypt the message + cipher = AES.new(bytearray(shared_secret), AES.MODE_CBC, bytearray(iv)) + utf8message = message.encode('utf-8') + padded_message = pad(utf8message, AES.block_size) + encrypted_msg = cipher.encrypt(padded_message) + + encrypted_msg_bech32 = bech32_encode("pzap", convertbits(encrypted_msg, 8, 5, True)) + iv_bech32 = bech32_encode("iv", convertbits(iv, 8, 5, True)) + + print("Encrypted Message:", encrypted_msg_bech32) + print("IV:", iv_bech32) + + return encrypted_msg_bech32 + "_" + iv_bech32 + + def decrypt_private_zap_message(msg: str, privkey: SecretKey, pubkey: PublicKey): shared_secret = nostr_sdk.generate_shared_key(privkey, pubkey) if len(shared_secret) != 16 and len(shared_secret) != 32: @@ -150,10 +173,10 @@ def decrypt_private_zap_message(msg: str, privkey: SecretKey, pubkey: PublicKey) return str(ex) -def zap(lud16: str, amount: int, content, recipient_pubkey, zapped_event, keys, dvm_config): +def zap(lud16: str, amount: int, content, zapped_event: Event, keys, dvm_config, zaptype="public"): if lud16.startswith("LNURL") or lud16.startswith("lnurl"): url = lnurl.decode(lud16) - elif '@' in lud16: #LNaddress + elif '@' in lud16: # LNaddress url = 'https://' + str(lud16).split('@')[1] + '/.well-known/lnurlp/' + str(lud16).split('@')[0] else: # No lud16 set or format invalid return None @@ -164,18 +187,31 @@ def zap(lud16: str, amount: int, content, recipient_pubkey, zapped_event, keys, encoded_lnurl = lnurl.encode(url) amount_tag = Tag.parse(['amount', str(amount * 1000)]) relays_tag = Tag.parse(['relays', str(dvm_config.RELAY_LIST)]) - p_tag = Tag.parse(['p', recipient_pubkey]) - e_tag = Tag.parse(['e', zapped_event]) + p_tag = Tag.parse(['p', zapped_event.pubkey().to_hex()]) + e_tag = Tag.parse(['e', zapped_event.id().to_hex()]) lnurl_tag = Tag.parse(['lnurl', encoded_lnurl]) + tags = [amount_tag, relays_tag, p_tag, e_tag, lnurl_tag] + + if zaptype == "private": + key_str = keys.secret_key().to_hex() + zapped_event.id().to_hex() + str(zapped_event.created_at().as_secs()) + encryption_key = sha256(key_str.encode('utf-8')).hexdigest() + + zap_request = EventBuilder(9733, content, + [p_tag, e_tag]).to_event(keys).as_json() + keys = Keys.from_sk_str(encryption_key) + encrypted_content = enrypt_private_zap_message(zap_request, keys.secret_key(), zapped_event.pubkey()) + anon_tag = Tag.parse(['anon', encrypted_content]) + tags.append(anon_tag) + content = "" zap_request = EventBuilder(9734, content, - [amount_tag, relays_tag, p_tag, e_tag, lnurl_tag]).to_event(keys).as_json() + tags).to_event(keys).as_json() response = requests.get(callback + "?amount=" + str(int(amount) * 1000) + "&nostr=" + urllib.parse.quote_plus( - zap_request) + "&lnurl=" + encoded_lnurl) + zap_request) + "&lnurl=" + encoded_lnurl) ob = json.loads(response.content) return ob["pr"] except Exception as e: print(e) - return None \ No newline at end of file + return None