Added string encoders to bech32 and can receive zap reciepts.

This commit is contained in:
Robert C. Martin 2023-04-17 14:12:29 -05:00
parent 00857e8398
commit f538ab26c0
15 changed files with 157 additions and 42 deletions

View File

@ -1,7 +1,7 @@
(ns more-speech.bech32-spec
(:require [speclj.core :refer :all]
[more-speech.bech32 :refer :all]
[more-speech.nostr.util :as util]))
(:require [more-speech.bech32 :refer :all]
[more-speech.nostr.util :as util]
[speclj.core :refer :all]))
(describe "bech32"
(context "charset translations"
@ -87,4 +87,33 @@
(should= pubkey (address->number "npub19mun7qwdyjf7qs3456u8kyxncjn5u2n7klpu4utgy68k4aenzj6synjnft")))
)
)
(context "decoding into strings"
(it "decodes into a string"
(let [lnurl (encode-str "lnurl" "this is the string")]
(should= "this is the string" (address->str lnurl))))
(it "decodes a long string"
(let [long-string "https://service.com/api?q=3fc3645b439ce8e7f2553a69e5267081d96dcd340693afabe04be7b0ccd178df"
lnurl (encode-str "lnurl" long-string)]
(should= long-string (address->str lnurl))))
(it "encodes and decodes a one byte string"
(let [known-string "t"
known-lnurl "lnurl1ws4pqzkn"]
(should= known-lnurl (encode-str "lnurl" known-string))
(should= known-string (address->str known-lnurl))))
(it "encodes and decodes a known short string"
(let [known-string "this is the string"
known-lnurl "lnurl1w35xjueqd9ejqargv5s8xarjd9hxw9sar9p"]
(should= known-lnurl (encode-str "lnurl" known-string))
(should= known-string (address->str known-lnurl))))
(it "encodes and decodes a known long string"
(let [known-string "https://service.com/api?q=3fc3645b439ce8e7f2553a69e5267081d96dcd340693afabe04be7b0ccd178df"
known-lnurl "lnurl1dp68gurn8ghj7um9wfmxjcm99e3k7mf0v9cxj0m385ekvcenxc6r2c35xvukxefcv5mkvv34x5ekzd3ev56nyd3hxqurzepexejxxepnxscrvwfnv9nxzcn9xq6xyefhvgcxxcmyxymnserxfq5fns"]
(should= known-string (address->str known-lnurl))
(should= known-lnurl (encode-str "lnurl" known-string))))
)
)

View File

@ -7,7 +7,7 @@
[more-speech.nostr.elliptic-signature :refer :all]
[more-speech.nostr.event-composers :refer :all]
[more-speech.nostr.event-composers :refer :all]
[more-speech.nostr.event-handlers :refer :all]
[more-speech.nostr.event-dispatcher :refer :all]
[more-speech.nostr.events :refer :all]
[more-speech.nostr.util :as util]
[more-speech.nostr.util :refer :all]

View File

@ -1,6 +1,6 @@
(ns more-speech.nostr.event-handlers-spec
(:require [speclj.core :refer :all]
[more-speech.nostr.event-handlers :as handlers]
[more-speech.nostr.event-dispatcher :as handlers]
[more-speech.db.gateway :as gateway]
[more-speech.db.in-memory :as in-memory]
[more-speech.config :as config]

View File

@ -3,7 +3,7 @@
[more-speech.db.gateway :as gateway]
[more-speech.db.in-memory :as in-memory]
[more-speech.nostr.events :refer :all]
[more-speech.nostr.event-handlers :refer :all]
[more-speech.nostr.event-dispatcher :refer :all]
[more-speech.nostr.elliptic-signature :refer :all]
[more-speech.nostr.util :refer :all]
[more-speech.mem :refer :all]

View File

@ -1,5 +1,6 @@
(ns more-speech.nostr.zaps-spec
(:require
[more-speech.bech32 :as bech32]
[more-speech.config :as config]
[more-speech.db.gateway :as gateway]
[more-speech.db.in-memory :as in-memory]
@ -102,6 +103,7 @@
amount 100
comment "comment"
lnurl "lnurl"
b32-lnurl (bech32/encode-str "lnurl" lnurl)
my-privkey 0xb0b
my-pubkey (util/bytes->num (es/get-pub-key (util/num->bytes 32 my-privkey)))
_ (set-mem :pubkey my-pubkey)
@ -120,7 +122,7 @@
(should= (util/get-now) created_at)
(should (contains? tags ["relays" "relay-r1" "relay-r2"]))
(should (contains? tags ["amount" "100"]))
(should (contains? tags ["lnurl" "lnurl1qqqxcmn4wfkqzejtan"]))
(should (contains? tags ["lnurl" b32-lnurl]))
(should (contains? tags ["p" (util/hexify recipient-id)]))
(should (contains? tags ["e" (util/hexify event-id)])))))

View File

@ -46,8 +46,14 @@
:else
cksum))
(defn parse-address [address]
(let [address (validate-address-length (.toLowerCase address))
(defn parse-address
([address]
(parse-address address #{}))
([address options]
(let [address (.toLowerCase address)
_ (when-not (contains? options :no-length-restriction)
(validate-address-length address))
hrp-end (find-separator-char address)
hrp (validate-hrp (subs address 0 hrp-end))
data-and-cksum (subs address (inc hrp-end))
@ -55,7 +61,7 @@
cksum (validate-cksum cksum)
data (apply str (drop-last 6 data-and-cksum))
data (validate-data data)]
[hrp data cksum]))
[hrp data cksum])))
(def generator [0x3b6a57b2, 0x26508e6d, 0x1ea119fa, 0x3d4233dd, 0x2a1462b3])
@ -120,8 +126,64 @@
(recur (quot id 32) (conj data (int (rem id 32))) (dec chars))))
cksum-data (create-checksum hrp data)
]
(str hrp "1" (apply str (map to-char (concat data cksum-data))))
))
(str hrp "1" (apply str (map to-char (concat data cksum-data))))))
; The bech32 coding converts streams of bytes into streams of 5-bit integers.
; The numbers 5 and 8 are mutually prime so there will usually be a remainder.
; That remainder is the number of zero bits on the end of the encoded stream.
; Thus, a single byte abcdefgh gets encoded as two five bit integer
; as abcde fgh00.
(defn- align-num [n the-string]
(let [n-bits (* 8 (count the-string))
r (rem n-bits 5)]
(if (zero? r)
n
(let [shift-n (- 5 r)
shift-factor (reduce * (repeat shift-n 2))]
(* n shift-factor)))))
(defn- str->num [the-string]
(loop [s the-string
n 0N]
(if (empty? s)
(align-num n the-string)
(recur (rest s) (+ (* 256 n) (int (first s)))))))
(defn- num->32bit-data [n]
(loop [n n
data (list)]
(if (zero? n)
data
(recur (quot n 32) (conj data (int (rem n 32)))))))
(defn encode-str
"create a bech32 representation of a string"
[hrp s]
(let [big-number (str->num s)
data (num->32bit-data big-number)
cksum-data (create-checksum hrp data)]
(str hrp "1" (apply str (map to-char (concat data cksum-data))))))
(defn address->str [address]
(let [[hrp data cksum] (parse-address address #{:no-length-restriction})
valid? (verify-checksum? [hrp data cksum])]
(if valid?
(let [values (map to-n data)
accumulator (reduce (fn [n value]
(+ value (* n 32)))
0N values)
n-bits (* 5 (count data))
shift-n (rem n-bits 8)
aligned-accumulator (quot accumulator (reduce * (repeat shift-n 2)))]
(loop [n aligned-accumulator
chars (list)]
(if (zero? n)
(apply str chars)
(recur (quot n 256) (conj chars (char (rem n 256)))))))
(throw (Exception. "bech32/address->str: invalid checksum")))
)
)
(defn address->number [address]
(let [[hrp data cksum] (parse-address address)

View File

@ -25,7 +25,7 @@
(log-pr 1 'main arg 'start)
(when (= "test" arg)
(config/test-run!))
(when (re-matches #"hours:\d+" arg)
(when (and (some? arg) (re-matches #"hours:\d+" arg))
(let [hours (Integer/parseInt (subs arg 6))]
(set-mem :request-hours-ago hours))
)

View File

@ -6,7 +6,7 @@
[more-speech.db.xtdb :as xtdb]
[more-speech.logger.default :refer [log-pr]]
[more-speech.mem :refer :all]
[more-speech.nostr.event-handlers :as handlers]
[more-speech.nostr.event-dispatcher :as handlers]
[more-speech.nostr.relays :as relays]
[more-speech.nostr.util :as util]
[more-speech.ui.formatter-util :as fu]

View File

@ -7,7 +7,7 @@
[more-speech.nostr
[util :as util]
[elliptic-signature :as ecc]
[event-handlers :as handlers]]
[event-dispatcher :as handlers]]
[more-speech.data-storage :as data-storage]
[more-speech.user-configuration :as user-configuration]
[more-speech.db.gateway :as gateway]

View File

@ -1,4 +1,4 @@
(ns more-speech.nostr.event-handlers
(ns more-speech.nostr.event-dispatcher
(:require [clojure.data.json :as json]
[more-speech.config :as config :refer [get-db]]
[more-speech.db.gateway :as gateway]
@ -8,9 +8,8 @@
[more-speech.nostr.elliptic-signature :as ecc]
[more-speech.nostr.events :as events]
[more-speech.nostr.relays :as relays]
[more-speech.nostr.util :refer :all]
[more-speech.nostr.util :refer [unhexify]]
[more-speech.nostr.util :as util])
[more-speech.nostr.zaps :as zaps]
[more-speech.nostr.util :refer :all :as util])
(:import (ecdhJava SECP256K1)))
(defprotocol event-handler
@ -126,6 +125,7 @@
3 (contact-list/process-contact-list db event)
4 (process-text-event db event url)
7 (process-reaction db event)
9735 (zaps/process-zap-receipt event)
nil))))
(defn decrypt-his-dm [event]

View File

@ -4,7 +4,7 @@
[more-speech.mem :refer :all]
[more-speech.mem :refer :all]
[more-speech.nostr.contact-list :as contact-list]
[more-speech.nostr.event-handlers :as handlers]
[more-speech.nostr.event-dispatcher :as handlers]
[more-speech.nostr.events :as events]
[more-speech.nostr.util :as util]
[more-speech.relay :as relay]

View File

@ -1,5 +1,7 @@
(ns more-speech.nostr.util
(:import (java.security MessageDigest SecureRandom)))
(:import (java.awt Toolkit)
(java.awt.datatransfer StringSelection)
(java.security MessageDigest SecureRandom)))
(defn num->bytes
"Returns the byte-array representation of n.
@ -87,3 +89,10 @@
(defn get-now []
(quot (get-now-ms) 1000))
(defn get-clipboard []
(.getSystemClipboard (Toolkit/getDefaultToolkit)))
(defn copy-to-clipboard [text]
(let [selection (StringSelection. text)]
(.setContents (get-clipboard) selection selection)))

View File

@ -7,10 +7,12 @@
[more-speech.config :refer [get-db]]
[more-speech.db.gateway :as gateway]
[more-speech.logger.default :refer [log-pr]]
[more-speech.mem :refer :all]
[more-speech.nostr.event-composers :as composers]
[more-speech.nostr.events :as events]
[more-speech.nostr.relays :as relays]
[more-speech.nostr.util :as util])
[more-speech.nostr.util :as util]
[more-speech.ui.formatter-util :as formatter-util])
(:use (seesaw [core]))
(:import (java.net URLEncoder)))
@ -52,7 +54,8 @@
(get-zap-address-from-profile event))))
(defn parse-lud16 [lud16]
(let [match (re-matches config/lud16-pattern lud16)]
(let [lud16 (.toLowerCase lud16)
match (re-matches config/lud16-pattern lud16)]
(if (some? match)
[(nth match 1) (nth match 2)]
(throw (Exception. (str "bad lud16 format " lud16)))))
@ -80,7 +83,7 @@
:content comment
:tags [(concat ["relays"] (relays/relays-for-reading))
["amount" (str amount)]
["lnurl" (bech32/encode "lnurl" (util/bytes->num (.getBytes lnurl)))]
["lnurl" (bech32/encode-str "lnurl" lnurl)]
["p" (util/hexify recipient)]
["e" (util/hexify (:id event))]]}
[_ request] (composers/body->event body)]
@ -114,10 +117,23 @@
_ (when (and (some? json-status) (= "ERROR" json-status))
(throw (Exception. (str "Invoice request error: " (get invoice-json "reason")))))
invoice (get invoice-json "pr")]
(prn 'zap-author invoice)
(prn 'zap-author 'metadata metadata))
(update-mem :pending-zaps assoc invoice {:id (:id event)
:amount amount})
(util/copy-to-clipboard invoice)
(alert (str "Invoice is copied to clipboard.\n"
"Paste it into your wallet and Zap!\n\n"
(formatter-util/abbreviate invoice 20)
(subs invoice (- (count invoice) 5)))))
)
(catch Exception e
(log-pr 1 'zap-author (.getMessage e))
(alert (str "Cannot zap. " (.getMessage e)))))
)
(alert (str "Cannot zap. " (.getMessage e))))))
(defn process-zap-receipt [event]
(prn 'zap-receipt event)
(let [[[receipt-invoice]] (events/get-tag event :bolt11)
transaction (get-mem [:pending-zaps receipt-invoice])]
(when (some? transaction)
(let [{:keys [id amount]} transaction]
(prn 'got-zap-receipt (util/hexify id) (/ amount 1000) 'sats)
(update-mem :pending-zaps dissoc receipt-invoice)))))

View File

@ -4,7 +4,7 @@
[more-speech.db.gateway :as gateway]
[more-speech.logger.default :refer [log-pr]]
[more-speech.mem :refer :all]
[more-speech.nostr.event-handlers :as handlers]
[more-speech.nostr.event-dispatcher :as handlers]
[more-speech.nostr.util :as util]
[more-speech.ui.formatter-util :as formatter-util]
[more-speech.ui.swing.article-panel :as article-panel]

View File

@ -3,7 +3,8 @@
[more-speech.config :as config]
[more-speech.db.gateway :as gateway]
[more-speech.mem :refer :all]
[more-speech.nostr.event-handlers :as event-handlers]
[more-speech.nostr.event-dispatcher :as event-handlers]
[more-speech.nostr.util :as util]
[more-speech.ui.swing.article-tree-util :as at-util])
(:use (seesaw [core]))
(:import (java.awt.datatransfer StringSelection)))
@ -96,12 +97,8 @@
(let [send-chan (get-mem :send-chan)]
(future (async/>!! send-chan [:relaunch]))))
(defn- get-clipboard []
(.getSystemClipboard (java.awt.Toolkit/getDefaultToolkit)))
(defn copy-to-clipboard [text _e]
(let [selection (StringSelection. text)]
(.setContents (get-clipboard) selection selection)))
(util/copy-to-clipboard text))
(defn load-event [id]
(let [event (gateway/get-event (config/get-db) id)