nostrb 0.1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/test/event.rb ADDED
@@ -0,0 +1,193 @@
1
+ require 'nostrb/event'
2
+ require_relative 'common.rb'
3
+ require 'minitest/autorun'
4
+
5
+ include Nostrb
6
+
7
+ describe Event do
8
+ def text_note(content = '')
9
+ Event.new(content, kind: 1, pk: Test::PK)
10
+ end
11
+
12
+ describe "class functions" do
13
+ it "computes a 32 byte digest of a JSON serialization" do
14
+ a = SignedEvent.serialize(Test::STATIC_HASH)
15
+ d = Event.digest(a)
16
+ expect(d).must_be_kind_of String
17
+ expect(d.length).must_equal 32
18
+ expect(d.encoding).must_equal Encoding::BINARY
19
+ end
20
+ end
21
+
22
+ describe "initialization" do
23
+ it "wraps a string of content" do
24
+ content = 'hello world'
25
+ expect(text_note(content).content).must_equal content
26
+ end
27
+
28
+ it "requires a _kind_ integer, defaulting to 1" do
29
+ expect(Event.new(kind: 0, pk: Test::PK).kind).must_equal 0
30
+ expect(Event.new(pk: Test::PK).kind).must_equal 1
31
+ end
32
+
33
+ it "requires a public key in binary format" do
34
+ expect(Test::EVENT.pk).must_equal Test::PK
35
+ expect {
36
+ Event.new(kind: 1, pk: SchnorrSig.bin2hex(Test::PK))
37
+ }.must_raise EncodingError
38
+ expect {
39
+ Event.new(kind: 1, pk: "0123456789abcdef".b)
40
+ }.must_raise SizeError
41
+ expect { Event.new }.must_raise
42
+ end
43
+ end
44
+
45
+ it "provides its content in a string context" do
46
+ s = text_note('hello').to_s
47
+ expect(s).must_equal 'hello'
48
+ end
49
+
50
+ it "serializes to an array starting with 0, length 6" do
51
+ a = text_note('hello').to_a
52
+ expect(a).must_be_kind_of Array
53
+ expect(a[0]).must_equal 0
54
+ expect(a.length).must_equal 6
55
+ expect(a[5]).must_equal 'hello'
56
+ end
57
+
58
+ it "has a pubkey in hex format" do
59
+ pubkey = Test::EVENT.pubkey
60
+ expect(pubkey).must_be_kind_of String
61
+ expect(pubkey.length).must_equal 64
62
+ end
63
+
64
+ it "requires a timestamp to create a SHA256 digest" do
65
+ e = Test::EVENT
66
+ d = e.digest(Time.now.to_i)
67
+ expect(d).must_be_kind_of String
68
+ expect(d.length).must_equal 32
69
+ expect(d.encoding).must_equal Encoding::BINARY
70
+ end
71
+
72
+ it "provides a SignedEvent when signed with a secret key" do
73
+ expect(Test::EVENT.sign(Test::SK)).must_be_kind_of SignedEvent
74
+ end
75
+
76
+ describe "event tags" do
77
+ it "supports tags in the form of Array[Array[String]]" do
78
+ e = text_note()
79
+ expect(e.tags).must_be_kind_of Array
80
+ expect(e.tags).must_be_empty
81
+
82
+ e.add_tag('tag', 'value')
83
+ expect(e.tags).wont_be_empty
84
+ expect(e.tags.length).must_equal 1
85
+
86
+ tags0 = e.tags[0]
87
+ expect(tags0.length).must_equal 2
88
+ expect(tags0[0]).must_equal 'tag'
89
+ expect(tags0[1]).must_equal 'value'
90
+
91
+ e.add_tag('foo', 'bar', 'baz')
92
+ expect(e.tags.length).must_equal 2
93
+
94
+ tags1 = e.tags[1]
95
+ expect(tags1.length).must_equal 3
96
+ expect(tags1[2]).must_equal 'baz'
97
+ end
98
+
99
+ it "references prior events" do
100
+ p = text_note()
101
+ s = p.sign(Test::SK)
102
+
103
+ e = text_note()
104
+ e.ref_event(s.id)
105
+ expect(e.tags).wont_be_empty
106
+ expect(e.tags.length).must_equal 1
107
+ expect(e.tags[0][0]).must_equal 'e'
108
+ expect(e.tags[0][1]).must_equal s.id
109
+ end
110
+
111
+ it "references known public keys" do
112
+ e = text_note()
113
+ pubkey = SchnorrSig.bin2hex Test::PK
114
+ e.ref_pubkey(pubkey)
115
+ expect(e.tags).wont_be_empty
116
+ expect(e.tags.length).must_equal 1
117
+ expect(e.tags[0][0]).must_equal 'p'
118
+ expect(e.tags[0][1]).must_equal pubkey
119
+ end
120
+ end
121
+ end
122
+
123
+ describe SignedEvent do
124
+ def signed_note(content = '')
125
+ Event.new(content, kind: 1, pk: Test::PK).sign(Test::SK)
126
+ end
127
+
128
+ describe "class functions" do
129
+ it "validates a JSON parsed hash" do
130
+ h = SignedEvent.validate!(Test::STATIC_HASH)
131
+ expect(h).must_be_kind_of Hash
132
+ %w[id pubkey kind content tags created_at sig].each { |k|
133
+ expect(h.key?(k)).must_equal true
134
+ }
135
+ end
136
+
137
+ it "verifies a JSON parsed hash" do
138
+ h = SignedEvent.verify(Test::STATIC_HASH)
139
+ expect(h).must_be_kind_of Hash
140
+ end
141
+
142
+ it "serializes a JSON parsed hash" do
143
+ a = SignedEvent.serialize(Test::STATIC_HASH)
144
+ expect(a).must_be_kind_of Array
145
+ expect(a.length).must_equal 6
146
+ end
147
+
148
+ it "digests a hash JSON parsed hash, which it will serialize" do
149
+ a = SignedEvent.serialize(Test::STATIC_HASH)
150
+ d = Event.digest(a)
151
+ d2 = SignedEvent.digest(Test::STATIC_HASH)
152
+ expect(d2).must_equal d
153
+ end
154
+ end
155
+
156
+ it "generates a timestamp at creation time" do
157
+ expect(signed_note().created_at).must_be_kind_of Integer
158
+ end
159
+
160
+ it "signs the event, given a private key in binary format" do
161
+ signed = signed_note()
162
+ expect(signed).must_be_kind_of SignedEvent
163
+ expect(signed.id).must_be_kind_of String
164
+ expect(signed.created_at).must_be_kind_of Integer
165
+
166
+ # check signature
167
+ signature = signed.signature
168
+ expect(signature).must_be_kind_of String
169
+ expect(signature.encoding).must_equal Encoding::BINARY
170
+ expect(signature.length).must_equal 64
171
+
172
+ # check sig hex
173
+ sig = signed.sig
174
+ expect(sig).must_be_kind_of String
175
+ expect(sig.encoding).wont_equal Encoding::BINARY
176
+ expect(sig.length).must_equal 128
177
+ end
178
+
179
+ it "has a formalized Key-Value format" do
180
+ h = signed_note().to_h
181
+ expect(h).must_be_kind_of Hash
182
+ expect(h.fetch "content").must_be_kind_of String
183
+ expect(h.fetch "pubkey").must_be_kind_of String
184
+ expect(h["pubkey"]).wont_be_empty
185
+ expect(h.fetch "kind").must_be_kind_of Integer
186
+ expect(h.fetch "tags").must_be_kind_of Array
187
+ expect(h.fetch "created_at").must_be_kind_of Integer
188
+ expect(h.fetch "id").must_be_kind_of String
189
+ expect(h["id"]).wont_be_empty
190
+ expect(h.fetch "sig").must_be_kind_of String
191
+ expect(h["sig"]).wont_be_empty
192
+ end
193
+ end
data/test/nostrb.rb ADDED
@@ -0,0 +1,87 @@
1
+ require 'nostrb'
2
+ require 'minitest/autorun'
3
+
4
+ describe Nostrb do
5
+ describe "module functions" do
6
+ describe "type enforcement" do
7
+ it "can check any class" do
8
+ str = 'asdf'
9
+ expect(Nostrb.check!(str, String)).must_equal str
10
+ expect { Nostrb.check!(str, Range) }.must_raise TypeError
11
+
12
+ range = (0..10)
13
+ expect(Nostrb.check!(range, Range)).must_equal range
14
+ expect { Nostrb.check!(range, Symbol) }.must_raise TypeError
15
+
16
+ sym = :symbol
17
+ expect(Nostrb.check!(sym, Symbol)).must_equal sym
18
+ expect { Nostrb.check!(sym, String) }.must_raise TypeError
19
+ end
20
+
21
+ it "validates a text (possibly hex) string" do
22
+ hex = "0123456789abcdef"
23
+
24
+ expect(Nostrb.txt!(hex)).must_equal hex
25
+ expect(Nostrb.txt!(hex, length: 16)).must_equal hex
26
+ expect { Nostrb.txt!(hex, length: 8) }.must_raise Nostrb::SizeError
27
+ expect { Nostrb.txt!("0123".b) }.must_raise EncodingError
28
+ end
29
+
30
+ it "enforces Integer class where expected" do
31
+ int = 1234
32
+ expect(Nostrb.int!(int)).must_equal int
33
+ expect { Nostrb.int!('1234') }.must_raise TypeError
34
+ end
35
+
36
+ it "enforces a particular tag structure where expected" do
37
+ # Array[Array[String]]
38
+ tags = [['a', 'b', 'c'], ['1', '2', '3', '4']]
39
+ expect(Nostrb.tags!(tags)).must_equal tags
40
+
41
+ [
42
+ ['a', 'b', 'c', '1' , '2', '3', '4'], # Array[String]
43
+ [['a', 'b', 'c'], [1, 2, 3, 4]], # Array[Array[String|Integer]]
44
+ ['a', 'b', 'c', ['1' , '2', '3', '4']],# Array[Array | String]
45
+ 'a', # String
46
+ [[:a, :b, :c], [1, 2, 3, 4]], # Array[Array[Symbol|String]]
47
+ ].each { |bad|
48
+ expect { Nostrb.tags!(bad) }.must_raise TypeError
49
+ }
50
+ end
51
+ end
52
+
53
+ describe "JSON I/O" do
54
+ it "parses a JSON string to a Ruby object" do
55
+ expect(Nostrb.parse('{}')).must_equal Hash.new
56
+ expect(Nostrb.parse('[]')).must_equal Array.new
57
+ end
58
+
59
+ it "generates JSON from a Ruby hash or array" do
60
+ expect(Nostrb.json({})).must_equal '{}'
61
+ expect(Nostrb.json([])).must_equal '[]'
62
+ end
63
+ end
64
+
65
+ describe "SHA256 digest" do
66
+ it "generates 32 bytes binary, given any string" do
67
+ strings = ["\x01\x02".b, '1234', 'asdf', '']
68
+ digests = strings.map { |s| Nostrb.digest(s) }
69
+
70
+ digests.each { |d|
71
+ expect(d).must_be_kind_of String
72
+ expect(d.encoding).must_equal Encoding::BINARY
73
+ expect(d.length).must_equal 32
74
+ }
75
+ end
76
+
77
+ it "generates the same output for the same input" do
78
+ strings = ["\x01\x02".b, '1234', 'asdf', '']
79
+ digests = strings.map { |s| Nostrb.digest(s) }
80
+
81
+ strings.each.with_index { |s, i|
82
+ expect(Nostrb.digest(s)).must_equal digests[i]
83
+ }
84
+ end
85
+ end
86
+ end
87
+ end
data/test/relay.rb ADDED
@@ -0,0 +1,360 @@
1
+ require 'nostrb/relay'
2
+ require 'nostrb/source'
3
+ require_relative 'common'
4
+ require 'minitest/autorun'
5
+
6
+ include Nostrb
7
+
8
+
9
+ # this can be set by GitHubActions
10
+ DB_FILE = ENV['INPUT_DB_FILE'] || 'testing.db'
11
+
12
+ # use SQLite backing
13
+ SQLite::Setup.new(DB_FILE).setup
14
+
15
+ describe Server do
16
+ def valid_response!(resp)
17
+ types = ["EVENT", "OK", "EOSE", "CLOSED", "NOTICE"]
18
+ expect(resp).must_be_kind_of Array
19
+ expect(resp.length).must_be :>=, 2
20
+ expect(resp.length).must_be :<=, 4
21
+ resp[0..1].each { |s| expect(s).must_be_kind_of String }
22
+ expect(resp[0].upcase).must_equal resp[0]
23
+ expect(types.include?(resp[0])).must_equal true
24
+ end
25
+
26
+ describe "class functions" do
27
+ it "has an EVENT response, given subscriber_id and requested event" do
28
+ sid = '1234'
29
+ resp = Server.event(sid, Test::SIGNED)
30
+ valid_response!(resp)
31
+ expect(resp[0]).must_equal "EVENT"
32
+ expect(resp[1]).must_equal sid
33
+ expect(resp[2]).must_be_kind_of Hash
34
+ expect(resp[2]).wont_be_empty
35
+ end
36
+
37
+ it "has an OK response, given an event_id" do
38
+ # positive ok
39
+ resp = Server.ok(Test::SIGNED.id)
40
+ valid_response!(resp)
41
+ expect(resp[0]).must_equal "OK"
42
+ expect(resp[1]).must_equal Test::SIGNED.id
43
+ expect(resp[2]).must_equal true
44
+ expect(resp[3]).must_be_kind_of String # empty by default
45
+
46
+ # negative ok
47
+ resp = Server.ok(Test::SIGNED.id, "error: testing", ok: false)
48
+ valid_response!(resp)
49
+ expect(resp[0]).must_equal "OK"
50
+ expect(resp[1]).must_equal Test::SIGNED.id
51
+ expect(resp[2]).must_equal false
52
+ expect(resp[3]).must_be_kind_of String
53
+ expect(resp[3]).wont_be_empty
54
+
55
+ # ok:false requires nonempty message
56
+ expect {
57
+ Server.ok(Test::SIGNED.id, "", ok: false)
58
+ }.must_raise FormatError
59
+ expect { Server.ok(Test::SIGNED.id, ok: false) }.must_raise FormatError
60
+ end
61
+
62
+ it "has an EOSE response to conclude a series of EVENT responses" do
63
+ sid = '1234'
64
+ resp = Server.eose(sid)
65
+ valid_response!(resp)
66
+ expect(resp[0]).must_equal "EOSE"
67
+ expect(resp[1]).must_equal sid
68
+ end
69
+
70
+ it "has a CLOSED response to shut down a subscriber" do
71
+ sid = '1234'
72
+ msg = "closed: bye"
73
+ resp = Server.closed(sid, msg)
74
+ valid_response!(resp)
75
+ expect(resp[0]).must_equal "CLOSED"
76
+ expect(resp[1]).must_equal sid
77
+ expect(resp[2]).must_equal msg
78
+ end
79
+
80
+ it "has a NOTICE response to provide any message to the user" do
81
+ msg = "all i ever really wanna do is get nice, " +
82
+ "get loose and goof this little slice of life"
83
+ resp = Server.notice(msg)
84
+ valid_response!(resp)
85
+ expect(resp[0]).must_equal "NOTICE"
86
+ expect(resp[1]).must_equal msg
87
+ end
88
+
89
+ it "formats Exceptions to a common string representation" do
90
+ r = RuntimeError.new("stuff")
91
+ expect(r).must_be_kind_of Exception
92
+ expect(Server.message(r)).must_equal "RuntimeError: stuff"
93
+
94
+ e = Nostrb::Error.new("things")
95
+ expect(e).must_be_kind_of Exception
96
+ expect(Server.message(e)).must_equal "Error: things"
97
+ end
98
+
99
+ it "uses NOTICE to return errors" do
100
+ e = RuntimeError.new "stuff"
101
+ resp = Server.error(e)
102
+ valid_response!(resp)
103
+ expect(resp[0]).must_equal "NOTICE"
104
+ expect(resp[1]).must_equal "RuntimeError: stuff"
105
+ end
106
+ end
107
+
108
+ it "has no initialization parameters" do
109
+ s = Server.new(DB_FILE)
110
+ expect(s).must_be_kind_of Server
111
+ end
112
+
113
+ # respond OK: true
114
+ it "has a single response to EVENT requests" do
115
+ json = Nostrb.json(Source.publish(Test::SIGNED))
116
+ responses = Server.new(DB_FILE).ingest(json)
117
+ expect(responses).must_be_kind_of Array
118
+ expect(responses.length).must_equal 1
119
+
120
+ resp = responses[0]
121
+ expect(resp).must_be_kind_of Array
122
+ expect(resp[0]).must_equal "OK"
123
+ expect(resp[1]).must_equal Test::SIGNED.id
124
+ expect(resp[2]).must_equal true
125
+ end
126
+
127
+ # store and retrieve with a subscription filter
128
+ it "stores inbound events" do
129
+ s = Server.new(DB_FILE)
130
+ sk, pk = SchnorrSig.keypair
131
+ e = Event.new('sqlite', pk: pk).sign(sk)
132
+ resp = s.ingest Nostrb.json(Source.publish(e))
133
+ expect(resp).must_be_kind_of Array
134
+ expect(resp[0]).must_be_kind_of Array
135
+ expect(resp[0][0]).must_equal "OK"
136
+
137
+ pubkey = SchnorrSig.bin2hex(pk)
138
+
139
+ f = Filter.new
140
+ f.add_authors pubkey
141
+ f.add_ids e.id
142
+
143
+ resp = s.ingest Nostrb.json(Source.subscribe(pubkey, f))
144
+ expect(resp).must_be_kind_of Array
145
+ expect(resp.length).must_equal 2
146
+ event, eose = *resp
147
+ expect(event[0]).must_equal 'EVENT'
148
+ expect(event[1]).must_equal pubkey
149
+ expect(event[2]).must_be_kind_of Hash
150
+ expect(event[2]['id']).must_equal e.id
151
+ expect(eose[0]).must_equal 'EOSE'
152
+ end
153
+
154
+ it "has multiple responses to REQ requets" do
155
+ s = Server.new(DB_FILE)
156
+ sk, pk = SchnorrSig.keypair
157
+ e = Event.new('first', pk: pk).sign(sk)
158
+ resp = s.ingest Nostrb.json(Source.publish(e))
159
+ expect(resp).must_be_kind_of Array
160
+ expect(resp[0]).must_be_kind_of Array
161
+ expect(resp[0][0]).must_equal "OK"
162
+
163
+ e2 = Event.new('second', pk: pk).sign(sk)
164
+ resp = s.ingest Nostrb.json(Source.publish(e2))
165
+ expect(resp).must_be_kind_of Array
166
+ expect(resp[0]).must_be_kind_of Array
167
+ expect(resp[0][0]).must_equal "OK"
168
+
169
+ # with no filters, nothing will match
170
+ sid = e.pubkey
171
+ responses = s.ingest(Nostrb.json(Source.subscribe(sid)))
172
+ expect(responses).must_be_kind_of Array
173
+ expect(responses.length).must_equal 1
174
+ resp = responses[0]
175
+ expect(resp).must_be_kind_of Array
176
+ expect(resp[0]).must_equal "EOSE"
177
+ expect(resp[1]).must_equal sid
178
+
179
+ # now add a filter based on pubkey
180
+ f = Filter.new
181
+ f.add_authors e.pubkey
182
+ f.add_ids e.id, e2.id
183
+
184
+ resp = s.ingest Nostrb.json(Source.subscribe(sid, f))
185
+ expect(resp).must_be_kind_of Array
186
+ expect(resp.length).must_equal 3
187
+
188
+ # remove EOSE and validate
189
+ eose = resp.pop
190
+ expect(eose).must_be_kind_of Array
191
+ expect(eose[0]).must_equal "EOSE"
192
+ expect(eose[1]).must_equal sid
193
+
194
+ # verify the response event ids
195
+ resp.each { |event|
196
+ expect(event).must_be_kind_of Array
197
+ expect(event[0]).must_equal "EVENT"
198
+ expect(event[1]).must_equal sid
199
+ hsh = event[2]
200
+ expect(hsh).must_be_kind_of Hash
201
+ expect(SignedEvent.validate!(hsh)).must_equal hsh
202
+ expect([e.id, e2.id]).must_include hsh["id"]
203
+ }
204
+ end
205
+
206
+ it "has a single response to CLOSE requests" do
207
+ s = Server.new(DB_FILE)
208
+ sid = Test::EVENT.pubkey
209
+ responses = s.ingest(Nostrb.json(Source.close(sid)))
210
+
211
+ # respond CLOSED
212
+ expect(responses).must_be_kind_of Array
213
+ expect(responses.length).must_equal 1
214
+
215
+ resp = responses[0]
216
+ expect(resp).must_be_kind_of Array
217
+ expect(resp[0]).must_equal "CLOSED"
218
+ expect(resp[1]).must_equal sid
219
+ end
220
+
221
+ describe "error handling" do
222
+ # invalid request type
223
+ it "handles unknown unknown request types with an error notice" do
224
+ a = Source.publish Test::SIGNED
225
+ a[0] = 'NONSENSE'
226
+ responses = Server.new(DB_FILE).ingest(Nostrb.json(a))
227
+ expect(responses).must_be_kind_of Array
228
+ expect(responses.length).must_equal 1
229
+
230
+ resp = responses[0]
231
+ expect(resp).must_be_kind_of Array
232
+ expect(resp[0]).must_equal "NOTICE"
233
+ expect(resp[1]).must_be_kind_of String
234
+ expect(resp[1]).wont_be_empty
235
+ end
236
+
237
+ # replace leading open brace with space
238
+ it "handles JSON parse errors with an error notice" do
239
+ j = Nostrb.json(Nostrb::Source.publish(Test::SIGNED))
240
+ expect(j[9]).must_equal '{'
241
+ j[9] = ' '
242
+ resp = Server.new(DB_FILE).ingest(j)
243
+ expect(resp).must_be_kind_of Array
244
+ expect(resp.length).must_equal 1
245
+
246
+ type, msg = *resp.first
247
+ expect(type).must_equal "NOTICE"
248
+ expect(msg).must_be_kind_of String
249
+ expect(msg).wont_be_empty
250
+ end
251
+
252
+ # add "stuff":"things"
253
+ it "handles unexpected fields with an error notice" do
254
+ a = Nostrb::Source.publish(Test::SIGNED)
255
+ expect(a[1]).must_be_kind_of Hash
256
+ a[1]["stuff"] = "things"
257
+
258
+ resp = Server.new(DB_FILE).ingest(Nostrb.json(a))
259
+ expect(resp).must_be_kind_of Array
260
+ expect(resp.length).must_equal 1
261
+
262
+ type, msg = *resp.first
263
+ expect(type).must_equal "NOTICE"
264
+ expect(msg).must_be_kind_of String
265
+ expect(msg).wont_be_empty
266
+ end
267
+
268
+ # remove "tags"
269
+ it "handles missing fields with an error notice" do
270
+ a = Nostrb::Source.publish(Test::SIGNED)
271
+ expect(a[1]).must_be_kind_of Hash
272
+ a[1].delete("tags")
273
+
274
+ resp = Server.new(DB_FILE).ingest(Nostrb.json(a))
275
+ expect(resp).must_be_kind_of Array
276
+ expect(resp.length).must_equal 1
277
+
278
+ type, msg = *resp.first
279
+ expect(type).must_equal "NOTICE"
280
+ expect(msg).must_be_kind_of String
281
+ expect(msg).wont_be_empty
282
+ end
283
+
284
+ # cut "id" in half
285
+ it "handles field format errors with an error notice" do
286
+ a = Nostrb::Source.publish(Test::SIGNED)
287
+ expect(a[1]).must_be_kind_of Hash
288
+ a[1]["id"] = a[1]["id"].slice(0, 32)
289
+
290
+ resp = Server.new(DB_FILE).ingest(Nostrb.json(a))
291
+ expect(resp).must_be_kind_of Array
292
+ expect(resp.length).must_equal 1
293
+
294
+ type, msg = *resp.first
295
+ expect(type).must_equal "NOTICE"
296
+ expect(msg).must_be_kind_of String
297
+ expect(msg).wont_be_empty
298
+ end
299
+
300
+ # random "sig"
301
+ it "handles invalid signature with OK:false" do
302
+ a = Nostrb::Source.publish(Test::SIGNED)
303
+ expect(a[1]).must_be_kind_of Hash
304
+ a[1]["sig"] = SchnorrSig.bin2hex(Random.bytes(64))
305
+
306
+ resp = Server.new(DB_FILE).ingest(Nostrb.json(a))
307
+ expect(resp).must_be_kind_of Array
308
+ expect(resp.length).must_equal 1
309
+
310
+ type, id, value, msg = *resp.first
311
+ expect(type).must_equal "OK"
312
+ expect(id).must_equal a[1]["id"]
313
+ expect(value).must_equal false
314
+ expect(msg).must_be_kind_of String
315
+ expect(msg).wont_be_empty
316
+ expect(msg).must_match(/SignatureCheck/)
317
+ end
318
+
319
+ # "id" and "sig" spoofed from another event
320
+ it "handles spoofed id with OK:false" do
321
+ orig = Source.publish(Test.new_event('orig'))
322
+ spoof = Source.publish(Test::SIGNED)
323
+
324
+ orig[1]["id"] = spoof[1]["id"]
325
+ orig[1]["sig"] = spoof[1]["sig"]
326
+
327
+ # now sig and id agree with each other, but not orig's content/metadata
328
+ # the signature should verify, but the id should not
329
+
330
+ resp = Server.new(DB_FILE).ingest(Nostrb.json(orig))
331
+ expect(resp).must_be_kind_of Array
332
+ expect(resp.length).must_equal 1
333
+
334
+ type, id, value, msg = *resp.first
335
+ expect(type).must_equal "OK"
336
+ expect(id).must_equal orig[1]["id"]
337
+ expect(value).must_equal false
338
+ expect(msg).must_be_kind_of String
339
+ expect(msg).wont_be_empty
340
+ expect(msg).must_match(/IdCheck/)
341
+ end
342
+
343
+ # random "id"
344
+ it "handles invalid id with OK:false" do
345
+ a = Source.publish(Test::SIGNED)
346
+ a[1]["id"] = SchnorrSig.bin2hex(Random.bytes(32))
347
+
348
+ resp = Server.new(DB_FILE).ingest(Nostrb.json(a))
349
+ expect(resp).must_be_kind_of Array
350
+ expect(resp.length).must_equal 1
351
+
352
+ type, id, value, msg = *resp.first
353
+ expect(type).must_equal "OK"
354
+ expect(id).must_equal a[1]["id"]
355
+ expect(value).must_equal false
356
+ expect(msg).must_be_kind_of String
357
+ expect(msg).wont_be_empty
358
+ end
359
+ end
360
+ end