Check signatures of incoming events. Move hex/num/bytes utilities from ecc to util.

This commit is contained in:
Robert C. Martin 2022-05-06 09:23:50 -05:00
parent c94bafd84a
commit bdd2f32b37
10 changed files with 137 additions and 209 deletions

View File

@ -1,65 +1,10 @@
(ns more-speech.nostr.ellliptic-signature-spec
(ns more-speech.nostr.elliptic-signature-spec
(:require [speclj.core :refer :all]
[more-speech.nostr.elliptic-signature :refer :all])
[more-speech.nostr.elliptic-signature :refer :all]
[more-speech.nostr.util :refer :all])
)
(describe "verification"
(it "decodes chars"
(should= 16rab (Integer/parseInt "ab" 16)))
(it "does hex-decode"
(should= "deadbeef" (bytes->hex-string (hex-string->bytes "deadbeef")))
(should= "aff9a9f017f32b2e8b60754a4102db9d9cf9ff2b967804b50e070780aa45c9a8"
(bytes->hex-string (hex-string->bytes "aff9a9f017f32b2e8b60754a4102db9d9cf9ff2b967804b50e070780aa45c9a8")))
)
(it "converts byte arrays to strings"
(should= "abcdef" (bytes->hex-string (hex-string->bytes "abcdef"))))
(it "xors bytes"
(should= "b2ce" (bytes->hex-string (xor-bytes (hex-string->bytes "af01")
(hex-string->bytes "1dcf"))))
(should= "000000000000"
(bytes->hex-string
(xor-bytes (hex-string->bytes "deadbeeffeed")
(hex-string->bytes "deadbeeffeed"))))
(should= "ffffffffffff"
(bytes->hex-string
(xor-bytes (hex-string->bytes "aaaacccc3333")
(hex-string->bytes "55553333cccc"))))
)
(it "should convert numbers to bytes"
(should= "1fcde563"
(bytes->hex-string
(num->bytes 4
(bytes->num
(hex-string->bytes "1fcde563")))))
(should= "ffcde563"
(bytes->hex-string
(num->bytes 4
(bytes->num
(hex-string->bytes "ffcde563")))))
)
;(it "signs"
; (let [private-key-1 (sha-256 (.getBytes "my private key 1" StandardCharsets/UTF_8))
; private-key-2 (sha-256 (.getBytes "my private key 2" StandardCharsets/UTF_8))
; public-key-1 (pub-key private-key-1)
; public-key-2 (pub-key private-key-2)
; message-1 (sha-256 (.getBytes "my message 1" StandardCharsets/UTF_8))
; message-2 (sha-256 (.getBytes "my message 2" StandardCharsets/UTF_8))
; sig-1 (sign private-key-1 message-1)
; sig-2 (sign private-key-2 message-2)
; ]
; (should= true (verify public-key-1 message-1 sig-1))
; (should= true (verify public-key-2 message-2 sig-2))
; (should-not (verify public-key-1 message-1 sig-2))
; (should-not (verify public-key-2 message-2 sig-1))
; (should-not (verify public-key-1 message-2 sig-1))
; (should-not (verify public-key-2 message-1 sig-2))))
(it "verifies Aaron Dixon's example."
(should= true
(do-verify

View File

@ -1,7 +1,8 @@
(ns more-speech.nostr.events_spec
(:require [speclj.core :refer :all]
[more-speech.nostr.events :refer :all]
[more-speech.nostr.elliptic-signature :as ecc]))
[more-speech.nostr.elliptic-signature :refer :all]
[more-speech.nostr.util :refer :all]))
(defrecord event-handler-dummy []
event-handler
@ -89,83 +90,83 @@
(describe "Composing outgoing events"
(it "composes an original message."
(let [private-key (ecc/num->bytes 64 314159)
event-state {:keys {:private-key (ecc/bytes->hex-string private-key)}}
public-key (ecc/get-pub-key private-key)
(let [private-key (num->bytes 64 314159)
event-state {:keys {:private-key (bytes->hex-string private-key)}}
public-key (get-pub-key private-key)
text "message text"
event (compose-text-event event-state text)
{:keys [pubkey created_at kind tags content id sig]} (second event)
now (quot (System/currentTimeMillis) 1000)]
(should= "EVENT" (first event))
(should= (ecc/bytes->hex-string public-key) pubkey)
(should= (bytes->hex-string public-key) pubkey)
(should (<= 0 (- now created_at) 1)) ;within one second.
(should= 1 kind)
(should= [] tags)
(should= text content)
(should (ecc/do-verify (ecc/hex-string->bytes id)
(should (do-verify (hex-string->bytes id)
public-key
(ecc/hex-string->bytes sig)))))
(hex-string->bytes sig)))))
(it "composes a reply to a root article."
(let [private-key (ecc/num->bytes 64 42)
(let [private-key (num->bytes 64 42)
root-id 7734
root-id-hex (hexify root-id)
root-author 99
event-state {:keys {:private-key (ecc/bytes->hex-string private-key)}
event-state {:keys {:private-key (bytes->hex-string private-key)}
:text-event-map {root-id {:pubkey root-author
:tags []}}}
public-key (ecc/get-pub-key private-key)
public-key (get-pub-key private-key)
text "message text"
event (compose-text-event event-state text root-id)
{:keys [pubkey created_at kind tags content id sig]} (second event)
now (quot (System/currentTimeMillis) 1000)]
(should= "EVENT" (first event))
(should= (ecc/bytes->hex-string public-key) pubkey)
(should= (bytes->hex-string public-key) pubkey)
(should (<= 0 (- now created_at) 1)) ;within one second.
(should= 1 kind)
(should= [[:e root-id-hex] [:p (hexify root-author)]] tags)
(should= text content)
(should (ecc/do-verify (ecc/hex-string->bytes id)
(should (do-verify (hex-string->bytes id)
public-key
(ecc/hex-string->bytes sig)))))
(hex-string->bytes sig)))))
(it "composes a reply to a non-root article."
(let [private-key (ecc/num->bytes 64 42)
(let [private-key (num->bytes 64 42)
root-child-id 7734
root-child-id-hex (hexify root-child-id)
root-child-author 88
root-id 1952
root-id-hex (hexify root-id)
root-author 99
event-state {:keys {:private-key (ecc/bytes->hex-string private-key)}
event-state {:keys {:private-key (bytes->hex-string private-key)}
:text-event-map {root-child-id {:pubkey root-child-author
:tags [[:e root-id-hex]
[:p (hexify root-author)]]}
root-id {:pubkey root-author
:tags []}}}
public-key (ecc/get-pub-key private-key)
public-key (get-pub-key private-key)
text "message text"
event (compose-text-event event-state text root-child-id)
{:keys [pubkey created_at kind tags content id sig]} (second event)
now (quot (System/currentTimeMillis) 1000)]
(should= "EVENT" (first event))
(should= (ecc/bytes->hex-string public-key) pubkey)
(should= (bytes->hex-string public-key) pubkey)
(should (<= 0 (- now created_at) 1)) ;within one second.
(should= 1 kind)
(should= [[:e root-id-hex] [:e root-child-id-hex]
[:p (hexify root-child-author)] [:p (hexify root-author)]] tags)
(should= text content)
(should (ecc/do-verify (ecc/hex-string->bytes id)
(should (do-verify (hex-string->bytes id)
public-key
(ecc/hex-string->bytes sig)))))
(hex-string->bytes sig)))))
(it "author is removed from replies"
(let [private-key (ecc/num->bytes 64 42)
author (ecc/bytes->num (ecc/get-pub-key private-key))
(let [private-key (num->bytes 64 42)
author (bytes->num (get-pub-key private-key))
root-id 7734
root-id-hex (hexify root-id)
root-author 99
event-state {:keys {:private-key (ecc/bytes->hex-string private-key)}
event-state {:keys {:private-key (bytes->hex-string private-key)}
:text-event-map {root-id {:pubkey root-author
:tags [[:p (hexify author)]]}}}
event (compose-text-event event-state "" root-id)
@ -174,22 +175,22 @@
(should= [[:e root-id-hex] [:p (hexify root-author)]] tags)))
(it "composes a message with a slash."
(let [private-key (ecc/num->bytes 64 42)
event-state {:keys {:private-key (ecc/bytes->hex-string private-key)}}
public-key (ecc/get-pub-key private-key)
(let [private-key (num->bytes 64 42)
event-state {:keys {:private-key (bytes->hex-string private-key)}}
public-key (get-pub-key private-key)
text "message/text"
event (compose-text-event event-state text)
{:keys [pubkey created_at kind tags content id sig]} (second event)
now (quot (System/currentTimeMillis) 1000)]
(should= "EVENT" (first event))
(should= (ecc/bytes->hex-string public-key) pubkey)
(should= (bytes->hex-string public-key) pubkey)
(should (<= 0 (- now created_at) 1)) ;within one second.
(should= 1 kind)
(should= [] tags)
(should= text content)
(should (ecc/do-verify (ecc/hex-string->bytes id)
(should (do-verify (hex-string->bytes id)
public-key
(ecc/hex-string->bytes sig)))))
(hex-string->bytes sig)))))
)
(describe "get references"

View File

@ -3,26 +3,42 @@
[more-speech.nostr.util :refer :all]))
(describe "Hex string and bytes conversions"
(it "does hex-decode"
(should= "deadbeef" (bytes->hex-string (hex-string->bytes "deadbeef")))
(should= "aff9a9f017f32b2e8b60754a4102db9d9cf9ff2b967804b50e070780aa45c9a8"
(bytes->hex-string (hex-string->bytes "aff9a9f017f32b2e8b60754a4102db9d9cf9ff2b967804b50e070780aa45c9a8")))
)
(it "decodes chars"
(should= 16rab (Integer/parseInt "ab" 16)))
(it "converts byte arrays to strings"
(should= "abcdef" (bytes->hex-string (hex-string->bytes "abcdef"))))
(it "does hex-decode"
(should= "deadbeef" (bytes->hex-string (hex-string->bytes "deadbeef")))
(should= "aff9a9f017f32b2e8b60754a4102db9d9cf9ff2b967804b50e070780aa45c9a8"
(bytes->hex-string (hex-string->bytes "aff9a9f017f32b2e8b60754a4102db9d9cf9ff2b967804b50e070780aa45c9a8")))
)
(it "should convert numbers to bytes"
(should= "1fcde563"
(bytes->hex-string
(num->bytes 4
(bytes->num
(hex-string->bytes "1fcde563")))))
(it "converts byte arrays to strings"
(should= "abcdef" (bytes->hex-string (hex-string->bytes "abcdef"))))
(should= "ffcde563"
(bytes->hex-string
(num->bytes 4
(bytes->num
(hex-string->bytes "ffcde563")))))
)
(it "xors bytes"
(should= "b2ce" (bytes->hex-string (xor-bytes (hex-string->bytes "af01")
(hex-string->bytes "1dcf"))))
(should= "000000000000"
(bytes->hex-string
(xor-bytes (hex-string->bytes "deadbeeffeed")
(hex-string->bytes "deadbeeffeed"))))
(should= "ffffffffffff"
(bytes->hex-string
(xor-bytes (hex-string->bytes "aaaacccc3333")
(hex-string->bytes "55553333cccc"))))
)
(it "should convert numbers to bytes"
(should= "1fcde563"
(bytes->hex-string
(num->bytes 4
(bytes->num
(hex-string->bytes "1fcde563")))))
(should= "ffcde563"
(bytes->hex-string
(num->bytes 4
(bytes->num
(hex-string->bytes "ffcde563")))))
)
)

View File

@ -1,9 +1,8 @@
;;Stories
;; - validate incoming messages.
;; - Add author/date, etc. to replies.
;; - Start checking sdefs in update.
;; - Clean up java schnorr library.
;; - Threading does not work quite right. Do some diagnosis.
;; - Threading needs some rethinking.
;; - Mark read and highlight properly.
;; - Save names and headers. Request after latest save.
;; - Consider subject/topic in the tags

View File

@ -1,71 +1,5 @@
(ns more-speech.nostr.elliptic-signature
(:import (java.security MessageDigest)
(schnorr Schnorr)))
(defn sha-256
"Returns the sha256 hash of the message.
Both the message and the hash are byte-arrays."
^bytes [^bytes message]
(let [digest (MessageDigest/getInstance "SHA-256")]
(.digest digest message)))
(defn num->bytes
"Returns the byte-array representation of n.
The array will have the specified length."
[length n]
(let [a (.toByteArray (biginteger n))
l (count a)
zeros (repeat (- length l) (byte 0))]
(if (> l length)
(byte-array (drop (- l length) (seq a)))
(byte-array (concat zeros a)))))
(defn bytes->num
"Returns a BigInteger from a byte-array."
^BigInteger [^bytes bytes]
(BigInteger. 1 bytes))
(defn bytes->hex-string
"Returns a string containing the hexadecimal
representation of the byte-array. This is the
inverse of hex-string->bytes."
[byte-array]
(let [byte-seq (for [i (range (alength byte-array))] (aget byte-array i))
byte-strings (map #(apply str (take-last 2 (format "%02x" %))) byte-seq)]
(apply str (apply concat byte-strings))))
(defn num32->hex-string [n]
(->> n (num->bytes 32) bytes->hex-string))
(defn ^bytes hex-string->bytes
"returns a byte-array containing the bytes described
by the hex-string. This is the inverse of bytes->hex-string."
[hex-string]
(let [byte-strings (map #(apply str %) (partition 2 hex-string))
byte-vector (map #(Integer/parseInt % 16) byte-strings)]
(byte-array byte-vector)))
(defn hex-string->num
"returns BigInteger from a hex string"
[hex-string]
(-> hex-string hex-string->bytes bytes->num))
(defn bytes= [^bytes b1 ^bytes b2]
(assert (= (alength b1) (alength b2)) "bytes= args not same size.")
(loop [index 0]
(if (= index (alength b1))
true
(if (= (aget b1 index) (aget b2 index))
(recur (inc index))
false)))
)
(defn xor-bytes [^bytes a ^bytes b]
(assert (= (alength a) (alength b)) "byte-wise-xor: arguments not same size.")
(let [result (byte-array (alength a))]
(doseq [i (range (alength a))]
(aset result i (byte (bit-xor (aget a i) (aget b i)))))
result))
(:import (schnorr Schnorr)))
(defn do-sign [message private-key aux-rand]
(Schnorr/sign message private-key aux-rand))

View File

@ -3,7 +3,8 @@
[clojure.data.json :as json]
[more-speech.nostr.util :refer [hex-string->num]]
[more-speech.nostr.elliptic-signature :as ecc]
[clojure.core.async :as async])
[clojure.core.async :as async]
[more-speech.nostr.util :as util])
(:import (java.nio.charset StandardCharsets)))
(s/def ::id number?)
@ -22,7 +23,7 @@
::tags
::references]))
(defn hexify [n]
(ecc/num32->hex-string n))
(util/num32->hex-string n))
(defprotocol event-handler
(handle-text-event [handler event])
@ -44,20 +45,27 @@
(defn process-event [{:keys [nicknames] :as event-state} event]
(let [_name-of (fn [pubkey] (get nicknames pubkey pubkey))
[_name _subscription-id inner-event :as _decoded-msg] event
{:strs [_id _pubkey _created_at kind _tags _content _sig]} inner-event]
(condp = kind
0 (process-name-event event-state inner-event)
3 (do
;(printf "%s: %s %s %s\n" kind (f/format-time created_at) (name-of pubkey) content)
event-state)
1 (do
;(printf "%s: %s %s %s\n" kind (f/format-time created_at) (name-of pubkey) (subs content 0 (min 50 (count content))))
(process-text-event event-state inner-event))
4 (do
;(printf "%s: %s %s %s\n" kind (f/format-time created_at) (name-of pubkey) content)
event-state)
(do (prn "unknown event: " event)
event-state))))
{:strs [id pubkey _created_at kind _tags _content sig]} inner-event
valid? (ecc/do-verify (util/hex-string->bytes id)
(util/hex-string->bytes pubkey)
(util/hex-string->bytes sig))]
(if (not valid?)
(do
(prn 'signature-verification-failed event)
event-state)
(condp = kind
0 (process-name-event event-state inner-event)
3 (do
;(printf "%s: %s %s %s\n" kind (f/format-time created_at) (name-of pubkey) content)
event-state)
1 (do
;(printf "%s: %s %s %s\n" kind (f/format-time created_at) (name-of pubkey) (subs content 0 (min 50 (count content))))
(process-text-event event-state inner-event))
4 (do
;(printf "%s: %s %s %s\n" kind (f/format-time created_at) (name-of pubkey) content)
event-state)
(do (prn "unknown event: " event)
event-state)))))
(defn process-name-event [event-state {:strs [_id pubkey _created_at _kind _tags content _sig] :as event}]
(try
@ -141,7 +149,7 @@
"returns byte array of id given the clojure form of the body"
[{:keys [pubkey created_at kind tags content]}]
(let [id-event (to-json [0 pubkey created_at kind tags content])
id (ecc/sha-256 (.getBytes id-event StandardCharsets/UTF_8))]
id (util/sha-256 (.getBytes id-event StandardCharsets/UTF_8))]
id)
)
@ -155,23 +163,23 @@
([event-state text reply-to-or-nil]
(let [private-key (get-in event-state [:keys :private-key])
private-key (ecc/hex-string->bytes private-key)
private-key (util/hex-string->bytes private-key)
pubkey (ecc/get-pub-key private-key)
root (get-reply-root event-state reply-to-or-nil)
tags (concat (make-event-reference-tags reply-to-or-nil root)
(make-people-reference-tags event-state pubkey reply-to-or-nil))
content text
now (quot (System/currentTimeMillis) 1000)
body {:pubkey (ecc/bytes->hex-string pubkey)
body {:pubkey (util/bytes->hex-string pubkey)
:created_at now
:kind 1
:tags tags
:content content}
id (make-id body)
aux-rand (ecc/num->bytes 32 (biginteger (System/currentTimeMillis)))
aux-rand (util/num->bytes 32 (biginteger (System/currentTimeMillis)))
signature (ecc/do-sign id private-key aux-rand)
event (assoc body :id (ecc/bytes->hex-string id)
:sig (ecc/bytes->hex-string signature))
event (assoc body :id (util/bytes->hex-string id)
:sig (util/bytes->hex-string signature))
]
["EVENT" event])))

View File

@ -90,7 +90,7 @@
(defn get-events [event-agent]
(let [conn (connect-to-relay (get relays 0) event-agent)
id "more-speech"
date (make-date "04/1/2022")
date (make-date "04/20/2022")
send-chan (:send-chan @event-agent)
]
(prn date (format-time date))

View File

@ -1,4 +1,5 @@
(ns more-speech.nostr.util)
(ns more-speech.nostr.util
(:import (java.security MessageDigest)))
(defn num->bytes
"Returns the byte-array representation of n.
@ -35,12 +36,36 @@
)
(defn hex-string->num
"Converts a hex string to a BigInteger"
^BigInteger [hex-string]
(bytes->num (hex-string->bytes hex-string))
"returns BigInteger from a hex string"
[hex-string]
(-> hex-string hex-string->bytes bytes->num))
(defn num32->hex-string [n]
"converts a number to a 32 byte hex-string"
(->> n (num->bytes 32) bytes->hex-string))
(defn bytes=
"compares two byte arrays for equality."
[^bytes b1 ^bytes b2]
(assert (= (alength b1) (alength b2)) "bytes= args not same size.")
(loop [index 0]
(if (= index (alength b1))
true
(if (= (aget b1 index) (aget b2 index))
(recur (inc index))
false)))
)
(defn num->hex-string
"converts a BigInteger to a hex-string"
[^BigInteger num]
(bytes->hex-string (num->bytes 32 num)))
(defn sha-256
"Returns the sha256 hash of the message.
Both the message and the hash are byte-arrays."
^bytes [^bytes message]
(let [digest (MessageDigest/getInstance "SHA-256")]
(.digest digest message)))
(defn xor-bytes [^bytes a ^bytes b]
(assert (= (alength a) (alength b)) "byte-wise-xor: arguments not same size.")
(let [result (byte-array (alength a))]
(doseq [i (range (alength a))]
(aset result i (byte (bit-xor (aget a i) (aget b i)))))
result))

View File

@ -42,7 +42,7 @@
(defn format-user-id [nicknames user-id]
(if (nil? user-id)
""
(abbreviate (get nicknames user-id (util/num->hex-string user-id)) 20)))
(abbreviate (get nicknames user-id (util/num32->hex-string user-id)) 20)))
(defn format-header [nicknames {:keys [pubkey created-at content] :as event}]
(if (nil? event)

View File

@ -1,8 +1,8 @@
(ns more-speech.ui.swing.article-tree
(:require [more-speech.nostr.events :as events]
[more-speech.ui.formatters :as formatters]
[more-speech.nostr.elliptic-signature :as ecc]
[more-speech.ui.config :as config])
[more-speech.ui.config :as config]
[more-speech.nostr.util :as util])
(:use [seesaw core font tree])
(:import (javax.swing.tree DefaultMutableTreeNode DefaultTreeModel TreePath)))
@ -36,7 +36,7 @@
(if (some? referent)
(let [replied-event (get text-map referent)]
(text! reply-to (format-user (:pubkey replied-event)))
(text! citing (formatters/abbreviate (ecc/num32->hex-string referent) 32)))
(text! citing (formatters/abbreviate (util/num32->hex-string referent) 32)))
(do (text! reply-to "")
(text! citing "")))
)))