-
Notifications
You must be signed in to change notification settings - Fork 1
/
07-private-chat.rb
183 lines (152 loc) · 5.3 KB
/
07-private-chat.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
# Private chat with another user
# Usage: ruby 07-private-chat.rb [pubkey]
require 'highline/import'
require 'schnorr'
require 'json'
require "base64"
require 'faye/websocket'
require 'eventmachine'
if ARGV[0]
$recipient_pub_key = ARGV[0]
end
puts "🟥 Paste your private hex key or press return to create a new random one"
$test_private_key = ask("> ") { |q| q.echo = '*'; q.validate = /.{64}/ }
puts
group = ECDSA::Group::Secp256k1
if $test_private_key.empty?
$test_private_key = (1 + SecureRandom.random_number(group.order - 1)).to_s(16)
puts "🟥 This is your private key, backup it!"
puts "#{$test_private_key}\n\n"
end
$test_pub_key = (group.generator.multiply_by_scalar($test_private_key.to_i(16)).x).to_s(16)
puts "🟩 Your public key is:\n#{$test_pub_key}\nYou can share it with your friends\n\n"
if !$recipient_pub_key
puts "🟥 Paste your friend hex pub key"
$recipient_pub_key = ask("> ") { |q| q.validate = /.{64}/ }
puts
end
relay_host = "wss://relay.damus.io" # 'wss://nostr-relay.wlvs.space' - 'ws://127.0.0.1'
def subscription_request
["REQ", SecureRandom.random_number.to_s,
{ "kinds": [4], "#p": [$test_pub_key, $recipient_pub_key], "since": (Time.now.utc - 60*60*24).to_i }
].to_json
end
def calculate_shared_key
group = ECDSA::Group::Secp256k1
test_key_hex = $test_private_key
test_ec = OpenSSL::PKey::EC.new('secp256k1')
test_ec.private_key = OpenSSL::BN.new(test_key_hex, 16)
test_pub_bn = OpenSSL::BN.new(group.generator.multiply_by_scalar(test_key_hex.to_i(16)).x.to_s(16), 16)
recipient_key_hex = '02' + $recipient_pub_key
recipient_ec = OpenSSL::PKey::EC.new('secp256k1')
recipient_pub_bn = OpenSSL::BN.new(recipient_key_hex, 16)
test_secret_point = OpenSSL::PKey::EC::Point.new(test_ec.group, recipient_pub_bn)
a_common_key = test_ec.dh_compute_key(test_secret_point)
a_common_key
end
$shared_key = calculate_shared_key
def decrypt_event(event)
data = event[2]
encrypted = data["content"].split("?iv=")[0]
iv = data["content"].split("?iv=")[1]
cipher = OpenSSL::Cipher::Cipher.new("aes-256-cbc")
cipher.decrypt
cipher.iv = Base64.decode64(iv)
cipher.key = $shared_key
decrypted = (cipher.update(Base64.decode64(encrypted)) + cipher.final).force_encoding('UTF-8')
end
def build_event(message)
event_created_at = Time.now.utc.to_i
event_type = 4
event_tags = [ ["p", $recipient_pub_key] ]
dm_message = message
dm_message = "\n" if dm_message.empty?
cipher = OpenSSL::Cipher::Cipher.new("aes-256-cbc")
cipher.encrypt
cipher.iv = iv = cipher.random_iv
cipher.key = $shared_key
event_message = cipher.update(dm_message)
event_message << cipher.final
event_message = Base64.encode64(event_message) + '?iv=' + Base64.encode64(iv)
event_message = event_message.gsub("\n", "")
serialized_event = [
0,
$test_pub_key,
event_created_at,
event_type,
event_tags,
event_message
]
serialized_event_json = JSON.dump(serialized_event)
serialized_event_sha256 = Digest::SHA256.hexdigest(serialized_event_json)
private_key = Array($test_private_key).pack("H*")
message = Array(serialized_event_sha256).pack("H*")
event_signature = Schnorr.sign(message, private_key).encode.unpack("H*")[0]
event = {
"id": serialized_event_sha256,
"pubkey": $test_pub_key,
"created_at": event_created_at,
"kind": event_type,
"tags": event_tags,
"content": event_message,
"sig": event_signature
}
return ["EVENT", event].to_json
end
def relay_connect(relay_host)
ws = Faye::WebSocket::Client.new(relay_host, nil, {ping: 60, :tls => {:verify_peer => false}})
sid = nil
ws.on :message do |e|
if !e.data.empty?
begin
event = JSON.parse(e.data)
# puts "---------- Event -----------------\n#{e.inspect}\n\n"
if event[0] == "EVENT" && event[2]["kind"] == 4
if event[2]["pubkey"] == $recipient_pub_key || (event[2]["pubkey"] == $test_pub_key &&
Time.now.to_i - event[2]["created_at"] > 3) # Hack -> Skip the just sent message to avoid a duplicate in the timeline
output = decrypt_event(event)
prev = (event[2]["pubkey"] == $recipient_pub_key) ? "🟠 " : "⚫️ "
puts prev + output + "\n\n" if output && !output.empty?
end
elsif event[0] == "EVENT" && event[2]["kind"] == 0
puts "🟪 Meta: " + event.inspect + "\n\n"
elsif event[0] == "EOSE"
# End Of Stored Event notice
puts "🟩 Ready to chat!\n\n"
elsif event[0] == "REQ"
# Subscription to events
puts "⬜️ Subscription: " + event.inspect + "\n\n"
elsif event[0] == "NOTICE"
# Subscription to events
puts "🟨 " + event[1] + "\n\n"
else
# puts "⬜️ " + event.inspect + "\n\n"
end
rescue JSON::ParserError
return "🟥 " + e.data.inspect + "\n\n"
end
end
end
ws.on :open do
ws.send subscription_request
sid = @channel.subscribe { |msg| ws.send build_event(msg) }
end
ws.on :close do |e|
puts "🟥 Reconnecting..."
sleep(5)
@channel.unsubscribe(sid)
relay_connect(relay_host)
end
ws.on :error do |e|
# puts "Error => #{e.inspect}"
end
end
EM.run {
@channel = EM::Channel.new
Thread.new {
loop do
@channel.push STDIN.gets.strip
end
}
relay_connect(relay_host)
}