1
local version = 022
2
local expect = require("cc.expect")
3
local cha = (function()
4
-- Chacha20 cipher in ComputerCraft
5
-- By Anavrins
6
-- For help and details, you can DM me on Discord (Anavrins#4600)
7
-- MIT License
8
-- Pastebin: https://pastebin.com/GPzf9JSa
9
-- Last updated: March 27 2020
10
11
local mod32 = 2^32
12
local bor = bit32.bor
13
local bxor = bit32.bxor
14
local band = bit32.band
15
local blshift = bit32.lshift
16
local brshift = bit32.arshift
17
18
local tau = {("expand 16-byte k"):byte(1,-1)}
19
local sigma = {("expand 32-byte k"):byte(1,-1)}
20
local null32 = {("A"):rep(32):byte(1,-1)}
21
local null12 = {("A"):rep(12):byte(1,-1)}
22
23
local function rotl(n, b)
24
local s = n/(2^(32-b))
25
local f = s%1
26
return (s-f) + f*mod32
27
end
28
29
local function quarterRound(s, a, b, c, d)
30
s[a] = (s[a]+s[b])%mod32; s[d] = rotl(bxor(s[d], s[a]), 16)
31
s[c] = (s[c]+s[d])%mod32; s[b] = rotl(bxor(s[b], s[c]), 12)
32
s[a] = (s[a]+s[b])%mod32; s[d] = rotl(bxor(s[d], s[a]), 8)
33
s[c] = (s[c]+s[d])%mod32; s[b] = rotl(bxor(s[b], s[c]), 7)
34
return s
35
end
36
37
local function hashBlock(state, rnd)
38
local s = {unpack(state)}
39
for i = 1, rnd do
40
local r = i%2==1
41
s = r and quarterRound(s, 1, 5, 9, 13) or quarterRound(s, 1, 6, 11, 16)
42
s = r and quarterRound(s, 2, 6, 10, 14) or quarterRound(s, 2, 7, 12, 13)
43
s = r and quarterRound(s, 3, 7, 11, 15) or quarterRound(s, 3, 8, 9, 14)
44
s = r and quarterRound(s, 4, 8, 12, 16) or quarterRound(s, 4, 5, 10, 15)
45
end
46
for i = 1, 16 do s[i] = (s[i]+state[i])%mod32 end
47
return s
48
end
49
50
local function LE_toInt(bs, i)
51
return (bs[i+1] or 0)+
52
blshift((bs[i+2] or 0), 8)+
53
blshift((bs[i+3] or 0), 16)+
54
blshift((bs[i+4] or 0), 24)
55
end
56
57
local function initState(key, nonce, counter)
58
local isKey256 = #key == 32
59
local const = isKey256 and sigma or tau
60
local state = {}
61
62
state[ 1] = LE_toInt(const, 0)
63
state[ 2] = LE_toInt(const, 4)
64
state[ 3] = LE_toInt(const, 8)
65
state[ 4] = LE_toInt(const, 12)
66
67
state[ 5] = LE_toInt(key, 0)
68
state[ 6] = LE_toInt(key, 4)
69
state[ 7] = LE_toInt(key, 8)
70
state[ 8] = LE_toInt(key, 12)
71
state[ 9] = LE_toInt(key, isKey256 and 16 or 0)
72
state[10] = LE_toInt(key, isKey256 and 20 or 4)
73
state[11] = LE_toInt(key, isKey256 and 24 or 8)
74
state[12] = LE_toInt(key, isKey256 and 28 or 12)
75
76
state[13] = counter
77
state[14] = LE_toInt(nonce, 0)
78
state[15] = LE_toInt(nonce, 4)
79
state[16] = LE_toInt(nonce, 8)
80
81
return state
82
end
83
84
local function serialize(state)
85
local r = {}
86
for i = 1, 16 do
87
r[#r+1] = band(state[i], 0xFF)
88
r[#r+1] = band(brshift(state[i], 8), 0xFF)
89
r[#r+1] = band(brshift(state[i], 16), 0xFF)
90
r[#r+1] = band(brshift(state[i], 24), 0xFF)
91
end
92
return r
93
end
94
95
local mt = {
96
__tostring = function(a) return string.char(unpack(a)) end,
97
__index = {
98
toHex = function(self) return ("%02x"):rep(#self):format(unpack(self)) end,
99
isEqual = function(self, t)
100
if type(t) ~= "table" then return false end
101
if #self ~= #t then return false end
102
local ret = 0
103
for i = 1, #self do
104
ret = bor(ret, bxor(self[i], t[i]))
105
end
106
return ret == 0
107
end
108
}
109
}
110
111
local function crypt(data, key, nonce, cntr, round)
112
assert(type(key) == "table", "ChaCha20: Invalid key format ("..type(key).."), must be table")
113
assert(type(nonce) == "table", "ChaCha20: Invalid nonce format ("..type(nonce).."), must be table")
114
assert(#key == 16 or #key == 32, "ChaCha20: Invalid key length ("..#key.."), must be 16 or 32")
115
assert(#nonce == 12, "ChaCha20: Invalid nonce length ("..#nonce.."), must be 12")
116
117
local data = type(data) == "table" and {unpack(data)} or {tostring(data):byte(1,-1)}
118
cntr = tonumber(cntr) or 1
119
round = tonumber(round) or 20
120
121
local out = {}
122
local state = initState(key, nonce, cntr)
123
local blockAmt = math.floor(#data/64)
124
for i = 0, blockAmt do
125
local ks = serialize(hashBlock(state, round))
126
state[13] = (state[13]+1) % mod32
127
128
local block = {}
129
for j = 1, 64 do
130
block[j] = data[((i)*64)+j]
131
end
132
for j = 1, #block do
133
out[#out+1] = bxor(block[j], ks[j])
134
end
135
136
--[[if i % 1000 == 0 then
137
os.queueEvent("")
138
os.pullEvent("")
139
end]]
140
end
141
return setmetatable(out, mt)
142
end
143
144
local function genNonce(len)
145
local nonce = {}
146
for i = 1, len do
147
nonce[i] = math.random(0, 0xFF)
148
end
149
return setmetatable(nonce, mt)
150
end
151
152
local obj = {}
153
local mtrng = {['__index'] = obj}
154
local function newRNG(seed)
155
local objVars = {}
156
objVars.seed = seed
157
objVars.cnt = 0
158
objVars.block = {}
159
return setmetatable(objVars, mtrng)
160
end
161
162
-- Specify how many bytes to return
163
-- 1 Byte is 8 bits, returns int between 0 and 255
164
-- 2 Bytes is 16 bits, returns int up to 65535
165
-- 4 Bytes is 32 bits, returns int up to 4294967295
166
-- Max of 6 Bytes is 48 bits, returns int up to 281474976710655
167
function obj:nextInt(byte)
168
if not byte or byte < 1 or byte > 6 then error("Can only return 1-6 bytes", 2) end
169
local output = 0
170
for i = 0, byte-1 do
171
if #self.block == 0 then
172
self.cnt = self.cnt + 1
173
self.block = crypt(null32, self.seed, null12, self.cnt)
174
end
175
local newByte = table.remove(self.block)
176
output = output + (newByte * (2^(8*i)))
177
end
178
return output
179
end
180
181
return {
182
crypt = crypt,
183
genNonce = genNonce,
184
newRNG = newRNG
185
}
186
end)()
187
188
-- START OF API --
189
190
settings.define("shironeko.pkey", {
191
description = "Your Shironeko pkey, obtained from \\sn me",
192
type = "string"
193
})
194
195
-- Wrap the modem with the given name with the given channel and privatekey
196
-- Creates a functionally secure modem peripheral with useful methods related to communication
197
local function wrap(name, channel, pkey)
198
local key = table.pack(pkey:gsub("-", ""):byte(1,-1))
199
local modem = assert(peripheral.wrap(name), "No such modem "..tostring(name))
200
modem.open(channel)
201
modem.name = name
202
local transmit = modem.transmit
203
modem.transmit = function(msg)
204
local nonce = cha.genNonce(12)
205
local data = cha.crypt(textutils.serialize(msg), key, nonce)
206
transmit(channel,channel,{
207
data = data,
208
nonce = nonce
209
})
210
end
211
modem.response = function(action, ok, result)
212
modem.transmit({
213
action = action,
214
ok = ok,
215
result = result
216
})
217
end
218
219
modem.listen = function(callback, timeout)
220
local ok = false
221
local out
222
parallel.waitForAny(function()
223
local done = false
224
while not done do
225
local _, side, ch, _, msg = os.pullEvent("modem_message")
226
if side == name and ch == channel then
227
local valid, response = pcall(function()
228
return textutils.unserialize(tostring(cha.crypt(msg.data, key, msg.nonce)))
229
end)
230
if valid then
231
if callback then
232
done, ok, out = callback(response)
233
else
234
done, ok, out = true, true, response
235
end
236
end
237
end
238
end
239
return ok, out
240
end,
241
function()
242
sleep(timeout or 5)
243
out = "timeout"
244
end)
245
return ok, out
246
end
247
modem.await = function(callback)
248
local done, ok = false, false
249
local out
250
while not done do
251
local _, side, ch, _, msg = os.pullEvent("modem_message")
252
if side == name and ch == channel then
253
local valid, response = pcall(function()
254
return textutils.unserialize(tostring(cha.crypt(msg.data, key, msg.nonce)))
255
end)
256
if valid then
257
callback(response)
258
end
259
end
260
end
261
end
262
return modem
263
end
264
265
local function responseCallback(action)
266
return function(data)
267
if data and data.action == action then
268
return true, data.ok, data.result
269
end
270
end
271
end
272
273
-- Initialize a modem for use with Shironeko. Should be wireless.
274
-- Provides the run, send, and close functions.
275
local function init(modemName)
276
expect(1, modemName, "string")
277
local pkey = assert(settings.get("shironeko.pkey"), "Missing shironeko.pkey setting. See \\sn me to access it.")
278
local modem = wrap(modemName, 4625, pkey)
279
280
-- Emit events into the CC event loop when Shironeko sends them. Run this in parallel with your program.
281
local function run()
282
modem.await(function(data)
283
if data.event then
284
os.queueEvent("shironeko_"..data.event, data.data)
285
end
286
end)
287
end
288
289
-- Possible events emitted by run:
290
-- shironeko_receive:
291
-- data.items (table): table of items sent. May contain duplicate items that merged into the same stack on delivery.
292
-- data.from (string): user that sent the items (the sender).
293
-- data.fromLabel (string): label from which items were sent (sender's label).
294
-- data.to (string): user these items were sent to (the receiver).
295
-- data.toLabel (string): label to which items were sent (receiver's label).
296
297
298
-- Send the provided slots to the provided user using the optional labels. fromLabel is one of the senders labels, toLabel is one of the recipients labels.
299
-- Returns:
300
-- boolean ok - Whether the operation was successful or not.
301
-- if ok:
302
-- table info - A table containing the same values as what would be provided by the recipient's shironeko_receive event, seen above.
303
-- if not ok:
304
-- string error - The nature of how the operation failed. See below function for possible errors.
305
local function send(user, slots, fromLabel, toLabel)
306
expect(1, user, "string")
307
expect(2, slots, "table", "number")
308
expect(3, fromLabel, "string", "nil")
309
expect(4, toLabel, "string", "nil")
310
modem.transmit({
311
action = "send",
312
user = user,
313
slots = slots,
314
fromLabel = fromLabel,
315
toLabel = toLabel,
316
version = version
317
})
318
return modem.listen(responseCallback("send"))
319
end
320
-- Possible errors for send:
321
-- "timeout" - The operation timed out/Shironeko didn't respond.
322
-- "version" - The API version used is out of date.
323
-- "no_such_label_sender" - The sender (you) did not have the given fromLabel.
324
-- "no_outbound_storage" - The sender's (you) label did not have a designated outbound storage.
325
-- "no_such_user" - The recipient does not exist and/or does not have a Shironeko user.
326
-- "no_such_label_recipient" - The recipient did not have the given toLabel.
327
-- "no_inbound_storage" - The recipient's label did not have a designated inbound storage.
328
-- "blocked" - You are blocked from performing this send operation by the recipient.
329
-- "same_storage" - The fromLabel and toLabel point to the same storage.
330
-- "recipient_full" - The recpient's inbound storage is full.
331
332
333
-- Sends an amount of items specified by a list of tables containing item names, counts, and optional nbt values.
334
-- In other words, the items in the list are identical to ones provided by generic inventory's .list() method.
335
-- The only difference being that you can specify a count greater than 64 to easily send multiple stacks of an item.
336
-- Returns:
337
-- boolean ok - Whether the operation was successful or not.
338
-- if ok:
339
-- table info - A table containing the same values as what would be provided by the recipient's shironeko_receive event, seen above.
340
-- if not ok:
341
-- string error - The nature of how the operation failed. See below function for possible errors.
342
local function sendItems(user, items, fromLabel, toLabel)
343
expect(1, user, "string")
344
expect(2, items, "table")
345
expect(3, fromLabel, "string", "nil")
346
expect(4, toLabel, "string", "nil")
347
modem.transmit({
348
action = "send_items",
349
user = user,
350
items = items,
351
fromLabel = fromLabel,
352
toLabel = toLabel,
353
version = version
354
})
355
return modem.listen(responseCallback("send_items"))
356
end
357
-- Possible errors for sendItems:
358
-- "timeout" - The operation timed out/Shironeko didn't respond.
359
-- "version" - The API version used is out of date.
360
-- "no_such_label_sender" - The sender (you) did not have the given fromLabel.
361
-- "no_outbound_storage" - The sender's (you) label did not have a designated outbound storage.
362
-- "no_such_user" - The recipient does not exist and/or does not have a Shironeko user.
363
-- "no_such_label_recipient" - The recipient did not have the given toLabel.
364
-- "no_inbound_storage" - The recipient's label did not have a designated inbound storage.
365
-- "blocked" - You are blocked from performing this send operation by the recipient.
366
-- "same_storage" - The fromLabel and toLabel point to the same storage.
367
-- "recipient_full" - The recpient's inbound storage is full.
368
369
370
-- Get a simple list of users who have a label "main" that can accept items.
371
-- Returns:
372
-- boolean ok - Whether the operation was successful or not.
373
-- if ok:
374
-- table users - List of users that have a "main" label that can accept items.
375
-- if not ok:
376
-- string error - The nature of how the operation failed. See below function for possible errors.
377
local function getUsers()
378
modem.transmit({
379
action = "get_users",
380
version = version
381
})
382
return modem.listen(responseCallback("get_users"))
383
end
384
-- Possible errors for send:
385
-- "timeout" - The operation timed out/Shironeko didn't respond.
386
-- "version" - The API version used is out of date.
387
388
-- Close the modem channel used by Shironeko
389
local function close()
390
modem.close(4625)
391
end
392
393
return {
394
run = run,
395
send = send,
396
sendItems = sendItems,
397
getUsers = getUsers,
398
close = close
399
}
400
end
401
402
return {
403
init = init,
404
}