-- Lantern trusted installer. Generated by tools/build.py.
local SOURCES={
["lantern.config"]=[=[return {version='0.2.0', sequence=2, target='1.120.2', root='/lantern', data='/lantern-data',
  hub='https://lantern.thultz.dev', protocol='lantern/1', discovery=47631, maxDocument=131072, chunk=8192,
  timeout=12, maxConnections=12, maxNodes=512, maxDepth=12}
]=],
["lantern.release"]=[=[local U=require('lantern.util')
local M={}
function M.verify(envelope,key)
  assert(type(envelope)=='table' and type(envelope.payload)=='string' and #envelope.payload<1500000,'Invalid release envelope')
  assert(type(envelope.signature)=='string' and #envelope.signature==128,'Invalid signature encoding')
  assert(require('ccryptolib.ed25519').verify(U.unhex(key),envelope.payload,U.unhex(envelope.signature)),'Release signature rejected')
  local release=assert(textutils.unserializeJSON(envelope.payload))
  assert(release.format==1 and type(release.version)=='string' and type(release.files)=='table','Invalid release')
  local count,total=0,0
  for path,file in pairs(release.files) do
    count=count+1; assert(count<=256,'Too many release files')
    assert(type(path)=='string' and path:sub(1,1)~='/' and U.path(path) and not path:find('//',1,true),'Unsafe release path')
    assert(type(file)=='table' and type(file.content)=='string' and #file.content<=262144,'Invalid release file')
    assert(file.size==#file.content,'File size mismatch')
    assert(U.hex(require('ccryptolib.sha256').digest(file.content))==file.sha256,'File digest mismatch: '..path)
    total=total+#file.content; assert(total<=1200000,'Release too large')
  end
  assert(release.files['lantern/main.lua'] and release.files['launcher.lua'],'Incomplete release')
  return release,total
end
return M
]=],
["lantern.ui"]=[=[local U=require('lantern.util')
local M={}
local palette={bg='f',panel='7',text='0',muted='8',accent='1',link='3',field='4',heading='1',normal='0'}
function M.new()
  local w,h=term.getSize(); local color=term.isColor(); local self={w=w,h=h,lines={},previous={}}
  local function c(name,bg) if not color then return bg and 'f' or '0' end; return palette[name] or name or (bg and 'f' or '0') end
  for y=1,h do self.lines[y]={string.rep(' ',w),string.rep(c('text'),w),string.rep(c('bg',true),w)} end
  function self:text(x,y,s,fg,bg)
    if y<1 or y>h then return end
    s=U.clean(s); if x<1 then s=s:sub(2-x); x=1 end; s=s:sub(1,w-x+1); if #s==0 then return end
    local row=self.lines[y]; local vals={s,string.rep(c(fg),#s),string.rep(c(bg,true),#s)}
    for i=1,3 do row[i]=row[i]:sub(1,x-1)..vals[i]..row[i]:sub(x+#s) end
  end
  function self:bar(y,bg) self:text(1,y,string.rep(' ',w),'text',bg) end
  function self:header(title)
    self:bar(1,'accent'); self:text(2,1,'/\\  LANTERN','bg','accent')
    self:text(2,3,title,'heading')
  end
  function self:footer(text) self:bar(h,'panel'); self:text(2,h,text,'text','panel') end
  function self:pixels(x,y,pixels)
    for i=1,#pixels do local v=pixels:sub(i,i); if v~=' ' then self:text(x+i-1,y,color and ' ' or '#','text',color and v or 'bg') end end
  end
  function self:flush()
    local last=M.last or {}
    for y,row in ipairs(self.lines) do
      if not last[y] or table.concat(row)~=table.concat(last[y]) then term.setCursorPos(1,y); term.blit(row[1],row[2],row[3]) end
    end
    M.last=self.lines; term.setCursorBlink(false)
  end
  return self
end
function M.message(title,message)
  local offset=0
  M.last=nil
  while true do
    local screen=M.new();screen:header(title)
    local rows={}
    for line in (tostring(message)..'\n'):gmatch('(.-)\n') do
      for _,part in ipairs(require('lantern.page').wrap(line,screen.w-2)) do rows[#rows+1]=part end
    end
    local visible=math.max(1,screen.h-5);offset=math.max(0,math.min(offset,#rows-visible))
    for i=1,visible do if rows[offset+i] then screen:text(2,3+i,rows[offset+i]) end end
    screen:footer('Up/Down scroll | Enter/Q back');screen:flush()
    local e,k=os.pullEvent()
    if e=='key' then
      if k==keys.enter or k==keys.q or k==keys.escape then M.last=nil;return end
      if k==keys.up then offset=offset-1 elseif k==keys.down then offset=offset+1 end
    elseif e=='mouse_scroll' then offset=offset+k
    elseif e=='mouse_click' then M.last=nil;return end
  end
end
function M.prompt(label,default,mask)
  M.last=nil;local screen=M.new();screen:header(label)
  screen:text(2,5,'Type below, then press Enter.','muted')
  screen:bar(7,'panel');screen:footer('Enter: confirm');screen:flush()
  term.setCursorPos(2,7);term.setBackgroundColor(colors.gray);term.setTextColor(colors.white)
  local s=read(mask,nil,nil,default or '');M.last=nil;return s
end
function M.choose(title,options)
  local selected=1
  while true do
    local screen=M.new(); screen:header(title)
    local start=math.max(1,selected-math.max(1,screen.h-7)+1)
    for i=start,math.min(#options,start+screen.h-7) do screen:text(2,5+i-start,(i==selected and '> ' or '  ')..options[i],i==selected and 'accent' or 'text') end
    screen:footer('Click to choose / Enter select / Q back'); screen:flush()
    local e,k,x,y=os.pullEvent()
    if e=='mouse_click' and y>=5 and y<screen.h then
      local choice=start+y-5;if options[choice] then M.last=nil;return choice end
    elseif e=='mouse_scroll' then selected=math.max(1,math.min(#options,selected+k))
    elseif e=='key' then
      if k==keys.up then selected=math.max(1,selected-1) elseif k==keys.down then selected=math.min(#options,selected+1)
      elseif k==keys.enter then return selected elseif k==keys.q or k==keys.escape then return nil end
    end
  end
end
function M.restore(fn)
  local fg,bg=term.getTextColor(),term.getBackgroundColor(); local x,y=term.getCursorPos(); local blink=term.getCursorBlink()
  local ok,err=pcall(fn)
  term.setBackgroundColor(bg); term.setTextColor(fg); term.clear(); term.setCursorPos(x,y); term.setCursorBlink(blink); M.last=nil
  if not ok and tostring(err)~='Terminated' then printError(err) end
  return ok,err
end
return M
]=],
["lantern.util"]=[=[local M = {}
function M.hex(s) return (s:gsub('.', function(c) return ('%02x'):format(c:byte()) end)) end
function M.unhex(s)
  assert(type(s)=='string' and #s%2==0 and not s:find('[^%x]'), 'Invalid hex')
  return (s:gsub('..', function(c) return string.char(tonumber(c,16)) end))
end
function M.path(s)
  if type(s)~='string' or #s>240 or s:find('[%z\1-\31\\:%%?#]') then return nil,'Invalid path' end
  for part in s:gmatch('[^/]+') do if part=='.' or part=='..' then return nil,'Unsafe path' end end
  return '/'..s:gsub('^/+',''):gsub('/+','/')
end
function M.slug(s) return type(s)=='string' and #s>0 and #s<=40 and s:match('^[a-z0-9][a-z0-9%-]*$')~=nil end
function M.address(s) return type(s)=='string' and #s==44 and s:match('^[A-Za-z0-9_%-]+=$')~=nil end
function M.now() return os.epoch('utc')/1000 end
function M.read(path,limit)
  if not fs.exists(path) or fs.isDir(path) then return nil,'File not found: '..path end
  if fs.getSize(path)>(limit or 262144) then return nil,'File too large' end
  local f,e=fs.open(path,'rb'); if not f then return nil,e end
  local s=f.readAll(); f.close(); return s
end
function M.write(path,s)
  fs.makeDir(fs.getDir(path)); local f=assert(fs.open(path,'wb')); f.write(s); f.close()
end
-- Recover only user-data transactions. Security state deliberately never rolls back.
function M.atomic(path,s)
  local tmp,bak=path..'.new',path..'.bak'
  if fs.exists(tmp) then fs.delete(tmp) end
  M.write(tmp,s)
  if fs.exists(bak) then fs.delete(bak) end
  if fs.exists(path) then fs.move(path,bak) end
  fs.move(tmp,path)
  if fs.exists(bak) then fs.delete(bak) end
end
function M.json(s)
  if type(s)~='string' or #s>262144 then return nil,'Document too large' end
  local ok,v=pcall(textutils.unserializeJSON,s)
  if not ok or type(v)~='table' then return nil,'Invalid JSON' end
  return v
end
function M.load(path,default)
  if not fs.exists(path) and fs.exists(path..'.bak') then fs.move(path..'.bak',path) end
  local s=M.read(path); local v=s and M.json(s)
  return v or default
end
function M.save(path,v) M.atomic(path,textutils.serializeJSON(v)) end
function M.clean(s) return tostring(s or ''):gsub('[%z\1-\31\127]',' ') end
return M
]=],
["ccryptolib.aead"]=[=[--- The ChaCha20Poly1305AEAD authenticated encryption with associated data (AEAD) construction.

local expect   = require "cc.expect".expect
local lassert = require "ccryptolib.internal.util".lassert
local packing  = require "ccryptolib.internal.packing"
local chacha20 = require "ccryptolib.chacha20"
local poly1305 = require "ccryptolib.poly1305"

local p8x1, fmt8x1 = packing.compilePack("<I8")
local u4x4, fmt4x4 = packing.compileUnpack("<I4I4I4I4")
local bxor = bit32.bxor

--- Encrypts a message.
--- @param key string A 32-byte random key.
--- @param nonce string A 12-byte per-message unique nonce.
--- @param message string The message to be encrypted.
--- @param aad string aad Arbitrary associated data to also authenticate.
--- @param rounds number? The number of ChaCha20 rounds to use. Defaults to 20.
--- @return string ctx The ciphertext.
--- @return string tag The 16-byte authentication tag.
local function encrypt(key, nonce, message, aad, rounds)
    expect(1, key, "string")
    lassert(#key == 32, "key length must be 32", 2)
    expect(2, nonce, "string")
    lassert(#nonce == 12, "nonce length must be 12", 2)
    expect(3, message, "string")
    expect(4, aad, "string")
    rounds = expect(5, rounds, "number", "nil") or 20
    lassert(rounds % 2 == 0, "round number must be even", 2)
    lassert(rounds >= 8, "round number must be no smaller than 8", 2)
    lassert(rounds <= 20, "round number must be no larger than 20", 2)

    -- Generate auth key and encrypt.
    local msgLong = ("\0"):rep(64) .. message
    local ctxLong = chacha20.crypt(key, nonce, msgLong, rounds, 0)
    local authKey = ctxLong:sub(1, 32)
    local ciphertext = ctxLong:sub(65)

    -- Authenticate.
    local pad1 = ("\0"):rep(-#aad % 16)
    local pad2 = ("\0"):rep(-#ciphertext % 16)
    local aadLen = p8x1("<I8", #aad)
    local ctxLen = p8x1("<I8", #ciphertext)
    local combined = aad .. pad1 .. ciphertext .. pad2 .. aadLen .. ctxLen
    local tag = poly1305.mac(authKey, combined)

    return ciphertext, tag
end

--- Decrypts a message.
--- @param key string The key used on encryption.
--- @param nonce string The nonce used on encryption.
--- @param tag string The authentication tag returned on encryption.
--- @param ciphertext string The ciphertext to be decrypted.
--- @param aad string The arbitrary associated data used on encryption.
--- @param rounds number The number of rounds used on encryption.
--- @return string? msg The decrypted plaintext. Or nil on auth failure.
local function decrypt(key, nonce, tag, ciphertext, aad, rounds)
    expect(1, key, "string")
    lassert(#key == 32, "key length must be 32", 2)
    expect(2, nonce, "string")
    lassert(#nonce == 12, "nonce length must be 12", 2)
    expect(3, tag, "string")
    lassert(#tag == 16, "tag length must be 16", 2)
    expect(4, ciphertext, "string")
    expect(5, aad, "string")
    rounds = expect(6, rounds, "number", "nil") or 20
    lassert(rounds % 2 == 0, "round number must be even", 2)
    lassert(rounds >= 8, "round number must be no smaller than 8", 2)
    lassert(rounds <= 20, "round number must be no larger than 20", 2)

    -- Generate auth key.
    local authKey = chacha20.crypt(key, nonce, ("\0"):rep(32), rounds, 0)

    -- Check tag.
    local pad1 = ("\0"):rep(-#aad % 16)
    local pad2 = ("\0"):rep(-#ciphertext % 16)
    local aadLen = p8x1(fmt8x1, #aad)
    local ctxLen = p8x1(fmt8x1, #ciphertext)
    local combined = aad .. pad1 .. ciphertext .. pad2 .. aadLen .. ctxLen
    local t1, t2, t3, t4 = u4x4(fmt4x4, tag, 1)
    local u1, u2, u3, u4 = u4x4(fmt4x4, poly1305.mac(authKey, combined), 1)
    local eq = bxor(t1, u1) + bxor(t2, u2) + bxor(t3, u3) + bxor(t4, u4)
    if eq ~= 0 then return nil end

    -- Decrypt
    return chacha20.crypt(key, nonce, ciphertext, rounds)
end

return {
    encrypt = encrypt,
    decrypt = decrypt,
}
]=],
["ccryptolib.blake3"]=[=[--- The BLAKE3 cryptographic hash function.

local expect = require "cc.expect".expect
local lassert = require "ccryptolib.internal.util".lassert
local packing = require "ccryptolib.internal.packing"

local unpack = unpack or table.unpack
local bxor = bit32.bxor
local rol = bit32.lrotate
local p16x4, fmt16x4 = packing.compilePack("<I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4")
local u16x4 = packing.compileUnpack(fmt16x4)
local u8x4, fmt8x4 = packing.compileUnpack("<I4I4I4I4I4I4I4I4")

local CHUNK_START = 0x01
local CHUNK_END = 0x02
local PARENT = 0x04
local ROOT = 0x08
local KEYED_HASH = 0x10
local DERIVE_KEY_CONTEXT = 0x20
local DERIVE_KEY_MATERIAL = 0x40

local IV = {
    0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a,
    0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19,
}

local function compress(h, msg, t, v14, v15, full)
    local h00, h01, h02, h03, h04, h05, h06, h07 = unpack(h)
    local v00, v01, v02, v03 = h00, h01, h02, h03
    local v04, v05, v06, v07 = h04, h05, h06, h07
    local v08, v09, v10, v11 = 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a
    local v12 = t % 2 ^ 32
    local v13 = (t - v12) * 2 ^ -32

    local m00, m01, m02, m03, m04, m05, m06, m07,
          m08, m09, m10, m11, m12, m13, m14, m15 = unpack(msg)

    local tmp
    for i = 1, 7 do
        v00 = v00 + v04 + m00 v12 = rol(bxor(v12, v00), 16)
        v08 = v08 + v12       v04 = rol(bxor(v04, v08), 20)
        v00 = v00 + v04 + m01 v12 = rol(bxor(v12, v00), 24)
        v08 = v08 + v12       v04 = rol(bxor(v04, v08), 25)

        v01 = v01 + v05 + m02 v13 = rol(bxor(v13, v01), 16)
        v09 = v09 + v13       v05 = rol(bxor(v05, v09), 20)
        v01 = v01 + v05 + m03 v13 = rol(bxor(v13, v01), 24)
        v09 = v09 + v13       v05 = rol(bxor(v05, v09), 25)

        v02 = v02 + v06 + m04 v14 = rol(bxor(v14, v02), 16)
        v10 = v10 + v14       v06 = rol(bxor(v06, v10), 20)
        v02 = v02 + v06 + m05 v14 = rol(bxor(v14, v02), 24)
        v10 = v10 + v14       v06 = rol(bxor(v06, v10), 25)

        v03 = v03 + v07 + m06 v15 = rol(bxor(v15, v03), 16)
        v11 = v11 + v15       v07 = rol(bxor(v07, v11), 20)
        v03 = v03 + v07 + m07 v15 = rol(bxor(v15, v03), 24)
        v11 = v11 + v15       v07 = rol(bxor(v07, v11), 25)

        v00 = v00 + v05 + m08 v15 = rol(bxor(v15, v00), 16)
        v10 = v10 + v15       v05 = rol(bxor(v05, v10), 20)
        v00 = v00 + v05 + m09 v15 = rol(bxor(v15, v00), 24)
        v10 = v10 + v15       v05 = rol(bxor(v05, v10), 25)

        v01 = v01 + v06 + m10 v12 = rol(bxor(v12, v01), 16)
        v11 = v11 + v12       v06 = rol(bxor(v06, v11), 20)
        v01 = v01 + v06 + m11 v12 = rol(bxor(v12, v01), 24)
        v11 = v11 + v12       v06 = rol(bxor(v06, v11), 25)

        v02 = v02 + v07 + m12 v13 = rol(bxor(v13, v02), 16)
        v08 = v08 + v13       v07 = rol(bxor(v07, v08), 20)
        v02 = v02 + v07 + m13 v13 = rol(bxor(v13, v02), 24)
        v08 = v08 + v13       v07 = rol(bxor(v07, v08), 25)

        v03 = v03 + v04 + m14 v14 = rol(bxor(v14, v03), 16)
        v09 = v09 + v14       v04 = rol(bxor(v04, v09), 20)
        v03 = v03 + v04 + m15 v14 = rol(bxor(v14, v03), 24)
        v09 = v09 + v14       v04 = rol(bxor(v04, v09), 25)

        if i ~= 7 then
            tmp = m02
            m02 = m03
            m03 = m10
            m10 = m12
            m12 = m09
            m09 = m11
            m11 = m05
            m05 = m00
            m00 = tmp

            tmp = m06
            m06 = m04
            m04 = m07
            m07 = m13
            m13 = m14
            m14 = m15
            m15 = m08
            m08 = m01
            m01 = tmp
        end
    end

    if full then
        return {
            bxor(v00, v08), bxor(v01, v09), bxor(v02, v10), bxor(v03, v11),
            bxor(v04, v12), bxor(v05, v13), bxor(v06, v14), bxor(v07, v15),
            bxor(v08, h00), bxor(v09, h01), bxor(v10, h02), bxor(v11, h03),
            bxor(v12, h04), bxor(v13, h05), bxor(v14, h06), bxor(v15, h07),
        }
    else
        return {
            bxor(v00, v08), bxor(v01, v09), bxor(v02, v10), bxor(v03, v11),
            bxor(v04, v12), bxor(v05, v13), bxor(v06, v14), bxor(v07, v15),
        }
    end
end

local function merge(cvl, cvr)
    for i = 1, 8 do cvl[i + 8] = cvr[i] end
    return cvl
end

local function blake3(iv, flags, msg, len)
    -- Set up the state.
    local stateCvs = {}
    local stateCv = iv
    local stateT = 0
    local stateN = 0
    local stateStart = CHUNK_START
    local stateEnd = 0

    -- Digest complete blocks.
    for i = 1, #msg - 64, 64 do
        -- Compress the block.
        local block = {u16x4(fmt16x4, msg, i)}
        local stateFlags = flags + stateStart + stateEnd
        stateCv = compress(stateCv, block, stateT, 64, stateFlags)
        stateStart = 0
        stateN = stateN + 1

        if stateN == 15 then
            -- Last block in chunk.
            stateEnd = CHUNK_END
        elseif stateN == 16 then
            -- Chunk complete, merge.
            local mergeCv = stateCv
            local mergeAmt = stateT + 1
            while mergeAmt % 2 == 0 do
                local block = merge(table.remove(stateCvs), mergeCv)
                mergeCv = compress(iv, block, 0, 64, flags + PARENT)
                mergeAmt = mergeAmt / 2
            end

            -- Push back.
            table.insert(stateCvs, mergeCv)

            -- Update state back to next chunk.
            stateCv = iv
            stateT = stateT + 1
            stateN = 0
            stateStart = CHUNK_START
            stateEnd = 0
        end
    end

    -- Pad the last message block.
    local lastLen = #msg == 0 and 0 or (#msg - 1) % 64 + 1
    local padded = msg:sub(-lastLen) .. ("\0"):rep(64)
    local last = {u16x4(fmt16x4, padded, 1)}

    -- Prepare output expansion state.
    local outCv, outBlock, outLen, outFlags
    if stateT > 0 then
        -- Root is a parent, digest last block now and merge parents.
        local stateFlags = flags + stateStart + CHUNK_END
        local mergeCv = compress(stateCv, last, stateT, lastLen, stateFlags)
        for i = #stateCvs, 2, -1 do
            local block = merge(stateCvs[i], mergeCv)
            mergeCv = compress(iv, block, 0, 64, flags + PARENT)
        end

        -- Set output state.
        outCv = iv
        outBlock = merge(stateCvs[1], mergeCv)
        outLen = 64
        outFlags = flags + ROOT + PARENT
    else
        -- Root block is in the first chunk, set output state.
        outCv = stateCv
        outBlock = last
        outLen = lastLen
        outFlags = flags + stateStart + CHUNK_END + ROOT
    end

    -- Expand output.
    local out = {}
    for i = 0, len / 64 do
        local md = compress(outCv, outBlock, i, outLen, outFlags, true)
        out[i + 1] = p16x4(fmt16x4, unpack(md))
    end

    return table.concat(out):sub(1, len)
end

--- Hashes data using BLAKE3.
--- @param message string The input message.
--- @param len number? The desired hash length, in bytes. Defaults to 32.
--- @return string hash The hash.
local function digest(message, len)
    expect(1, message, "string")
    len = expect(2, len, "number", "nil") or 32
    lassert(len % 1 == 0, "desired output length must be an integer", 2)
    lassert(len >= 1, "desired output length must be positive", 2)
    return blake3(IV, 0, message, len)
end

--- Performs a keyed hash.
--- @param key string A 32-byte random key.
--- @param message string The input message.
--- @param len number? The desired hash length, in bytes. Defaults to 32.
--- @return string hash The keyed hash.
local function digestKeyed(key, message, len)
    expect(1, key, "string")
    lassert(#key == 32, "key length must be 32", 2)
    expect(2, message, "string")
    len = expect(3, len, "number", "nil") or 32
    lassert(len % 1 == 0, "desired output length must be an integer", 2)
    lassert(len >= 1, "desired output length must be positive", 2)
    return blake3({u8x4(fmt8x4, key, 1)}, KEYED_HASH, message, len)
end

--- Makes a context-based key derivation function (KDF).
--- @param context string The context for the KDF.
--- @return fun(material: string, len: number?): string kdf The KDF.
local function deriveKey(context)
    expect(1, context, "string")
    local iv = {u8x4(fmt8x4, blake3(IV, DERIVE_KEY_CONTEXT, context, 32), 1)}

    --- Derives a key.
    --- @param material string The keying material.
    --- @param len number? The desired hash length, in bytes. Defaults to 32.
    return function(material, len)
        expect(1, material, "string")
        len = expect(2, len, "number", "nil") or 32
        lassert(len % 1 == 0, "desired output length must be an integer", 2)
        lassert(len >= 1, "desired output length must be positive", 2)
        return blake3(iv, DERIVE_KEY_MATERIAL, material, len)
    end
end

return {
    digest = digest,
    digestKeyed = digestKeyed,
    deriveKey = deriveKey,
}
]=],
["ccryptolib.chacha20"]=[=[--- The ChaCha20 stream cipher.

local expect  = require "cc.expect".expect
local lassert = require "ccryptolib.internal.util".lassert
local packing = require "ccryptolib.internal.packing"

local bxor = bit32.bxor
local rol = bit32.lrotate
local u8x4, fmt8x4 = packing.compileUnpack("<I4I4I4I4I4I4I4I4")
local u3x4, fmt3x4 = packing.compileUnpack("<I4I4I4")
local p16x4, fmt16x4 = packing.compilePack("<I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4")
local u16x4 = packing.compileUnpack(fmt16x4)

--- Encrypts/Decrypts data using ChaCha20.
--- @param key string A 32-byte random key.
--- @param nonce string A 12-byte per-message unique nonce.
--- @param message string A plaintext or ciphertext.
--- @param rounds number? The number of ChaCha20 rounds to use. Defaults to 20.
--- @param offset number? The block offset to generate the keystream at. Defaults to 1.
--- @return string out The resulting ciphertext or plaintext.
local function crypt(key, nonce, message, rounds, offset)
    expect(1, key, "string")
    lassert(#key == 32, "key length must be 32", 2)
    expect(2, nonce, "string")
    lassert(#nonce == 12, "nonce length must be 12", 2)
    expect(3, message, "string")
    rounds = expect(4, rounds, "number", "nil") or 20
    lassert(rounds % 2 == 0, "round number must be even", 2)
    lassert(rounds >= 8, "round number must be no smaller than 8", 2)
    lassert(rounds <= 20, "round number must be no larger than 20", 2)
    offset = expect(5, offset, "number", "nil") or 1
    lassert(offset % 1 == 0, "offset must be an integer", 2)
    lassert(offset >= 0, "offset must be nonnegative", 2)
    lassert(#message + 64 * offset <= 2 ^ 38, "offset too large", 2)

    -- Build the state block.
    local i0, i1, i2, i3 = 0x61707865, 0x3320646e, 0x79622d32, 0x6b206574
    local k0, k1, k2, k3, k4, k5, k6, k7 = u8x4(fmt8x4, key, 1)
    local cr, n0, n1, n2 = offset, u3x4(fmt3x4, nonce, 1)

    -- Pad the message.
    local padded = message .. ("\0"):rep(-#message % 64)

    -- Expand and combine.
    local out = {}
    local idx = 1
    for i = 1, #padded / 64 do
        -- Copy the block.
        local s00, s01, s02, s03 = i0, i1, i2, i3
        local s04, s05, s06, s07 = k0, k1, k2, k3
        local s08, s09, s10, s11 = k4, k5, k6, k7
        local s12, s13, s14, s15 = cr, n0, n1, n2

        -- Iterate.
        for _ = 1, rounds, 2 do
            s00 = s00 + s04 s12 = rol(bxor(s12, s00), 16)
            s08 = s08 + s12 s04 = rol(bxor(s04, s08), 12)
            s00 = s00 + s04 s12 = rol(bxor(s12, s00), 8)
            s08 = s08 + s12 s04 = rol(bxor(s04, s08), 7)

            s01 = s01 + s05 s13 = rol(bxor(s13, s01), 16)
            s09 = s09 + s13 s05 = rol(bxor(s05, s09), 12)
            s01 = s01 + s05 s13 = rol(bxor(s13, s01), 8)
            s09 = s09 + s13 s05 = rol(bxor(s05, s09), 7)

            s02 = s02 + s06 s14 = rol(bxor(s14, s02), 16)
            s10 = s10 + s14 s06 = rol(bxor(s06, s10), 12)
            s02 = s02 + s06 s14 = rol(bxor(s14, s02), 8)
            s10 = s10 + s14 s06 = rol(bxor(s06, s10), 7)

            s03 = s03 + s07 s15 = rol(bxor(s15, s03), 16)
            s11 = s11 + s15 s07 = rol(bxor(s07, s11), 12)
            s03 = s03 + s07 s15 = rol(bxor(s15, s03), 8)
            s11 = s11 + s15 s07 = rol(bxor(s07, s11), 7)

            s00 = s00 + s05 s15 = rol(bxor(s15, s00), 16)
            s10 = s10 + s15 s05 = rol(bxor(s05, s10), 12)
            s00 = s00 + s05 s15 = rol(bxor(s15, s00), 8)
            s10 = s10 + s15 s05 = rol(bxor(s05, s10), 7)

            s01 = s01 + s06 s12 = rol(bxor(s12, s01), 16)
            s11 = s11 + s12 s06 = rol(bxor(s06, s11), 12)
            s01 = s01 + s06 s12 = rol(bxor(s12, s01), 8)
            s11 = s11 + s12 s06 = rol(bxor(s06, s11), 7)

            s02 = s02 + s07 s13 = rol(bxor(s13, s02), 16)
            s08 = s08 + s13 s07 = rol(bxor(s07, s08), 12)
            s02 = s02 + s07 s13 = rol(bxor(s13, s02), 8)
            s08 = s08 + s13 s07 = rol(bxor(s07, s08), 7)

            s03 = s03 + s04 s14 = rol(bxor(s14, s03), 16)
            s09 = s09 + s14 s04 = rol(bxor(s04, s09), 12)
            s03 = s03 + s04 s14 = rol(bxor(s14, s03), 8)
            s09 = s09 + s14 s04 = rol(bxor(s04, s09), 7)
        end

        -- Decode message block.
        local m00, m01, m02, m03, m04, m05, m06, m07
        local m08, m09, m10, m11, m12, m13, m14, m15

        m00, m01, m02, m03, m04, m05, m06, m07,
        m08, m09, m10, m11, m12, m13, m14, m15, idx =
            u16x4(fmt16x4, padded, idx)

        -- Feed-forward and combine.
        out[i] = p16x4(fmt16x4,
            bxor(m00, s00 + i0), bxor(m01, s01 + i1),
            bxor(m02, s02 + i2), bxor(m03, s03 + i3),
            bxor(m04, s04 + k0), bxor(m05, s05 + k1),
            bxor(m06, s06 + k2), bxor(m07, s07 + k3),
            bxor(m08, s08 + k4), bxor(m09, s09 + k5),
            bxor(m10, s10 + k6), bxor(m11, s11 + k7),
            bxor(m12, s12 + cr), bxor(m13, s13 + n0),
            bxor(m14, s14 + n1), bxor(m15, s15 + n2)
        )

        -- Increment counter.
        cr = cr + 1
    end

    return table.concat(out):sub(1, #message)
end

return {
    crypt = crypt,
}
]=],
["ccryptolib.config"]=[=[local expect = require "cc.expect".expect

local usePeripheralsValue = true

--- Sets whether to use peripheral calls for computation. Defaults to true.
---
--- If this is true, ccryptolib will search for compatible peripherals every few
--- seconds. If a compatible peripheral is found, ccryptolib will offload some
--- of its computation to it, improving performance.
---
--- No peripheral searches are performed until you first call a ccryptolib
--- function. Setting this value to false beforehand guarantees no searches will
--- ever be made.
---
--- @param value boolean? The new setting. If nil then no changes are made.
--- @return boolean value The current setting. Includes any changes made.
local function usePeripherals(value)
    expect(1, value, "boolean", "nil")
    if value ~= nil then usePeripheralsValue = value end
    return usePeripheralsValue
end

return {
    usePeripherals = usePeripherals,
}
]=],
["ccryptolib.ed25519"]=[=[--- The Ed25519 digital signature scheme.

local expect = require "cc.expect".expect
local lassert = require "ccryptolib.internal.util".lassert
local hw = require "ccryptolib.internal.hw"
local fq = require "ccryptolib.internal.fq"
local sha512 = require "ccryptolib.internal.sha512"
local ed = require "ccryptolib.internal.edwards25519"
local random = require "ccryptolib.random"

--- Computes a public key from a secret key.
--- @param sk string A random 32-byte secret key.
--- @return string pk The matching 32-byte public key.
local function publicKey(sk)
    expect(1, sk, "string")
    assert(#sk == 32, "secret key length must be 32")
    local ok, out = hw.ed25519PublicKey(sk)
    if ok then return out end

    local h = sha512.digest(sk)
    local x = fq.decodeClamped(h:sub(1, 32))

    return ed.encode(ed.mulG(fq.bits(x)))
end

--- Signs a message.
--- @param sk string The signer's secret key.
--- @param pk string The signer's public key.
--- @param msg string The message to be signed.
--- @return string sig The 64-byte signature on the message.
local function sign(sk, pk, msg)
    expect(1, sk, "string")
    lassert(#sk == 32, "secret key length must be 32", 2)
    expect(2, pk, "string")
    lassert(#pk == 32, "public key length must be 32", 2)
    expect(3, msg, "string")

    -- Note: We use randomization as a side channel attack mitigation attempt.
    -- Calls to other libraries will almost surely use the standardized
    -- deterministic signatues instead.
    local ok, out = hw.ed25519Sign(msg, sk)
    if ok then return out end

    -- Secret key.
    local h = sha512.digest(sk)
    local x = fq.decodeClamped(h:sub(1, 32))

    -- Commitment.
    local k = fq.decodeWide(random.random(64))
    local r = ed.mulG(fq.bits(k))
    local rStr = ed.encode(r)

    -- Challenge.
    local e = fq.decodeWide(sha512.digest(rStr .. pk .. msg))

    -- Response.
    local m = fq.decodeWide(random.random(64))
    local s = fq.sub(fq.add(k, fq.mul(fq.add(x, m), e)), fq.mul(m, e))
    local sStr = fq.encode(s)

    return rStr .. sStr
end

--- Verifies a signature on a message.
--- @param pk string The signer's public key.
--- @param msg string The signed message.
--- @param sig string The alleged signature.
--- @return boolean valid Whether the signature is valid or not.
local function verify(pk, msg, sig)
    expect(1, pk, "string")
    lassert(#pk == 32, "public key length must be 32", 2) --- @cast pk String32
    expect(2, msg, "string")
    expect(3, sig, "string")
    lassert(#sig == 64, "signature length must be 64", 2)

    -- Note: The library verifier may use strict signature verification, which
    -- rejects signatures that we don't with some adversarial signatures. We
    -- make no guarantees on those so we can get by regardless.
    local ok, out = hw.ed25519Verify(msg, sig, pk)
    if ok then return out end

    local y = ed.decode(pk)
    if not y then return false end

    local rStr = sig:sub(1, 32)
    local sStr = sig:sub(33)

    local e = fq.decodeWide(sha512.digest(rStr .. pk .. msg))

    local gs = ed.mulG(fq.bits(fq.decode(sStr)))
    local ye = ed.mul(y, fq.bits(e))
    local rv = ed.sub(gs, ed.niels(ye))

    return ed.encode(rv) == rStr
end

return {
    publicKey = publicKey,
    sign = sign,
    verify = verify,
}
]=],
["ccryptolib.internal.curve25519"]=[=[--- Point arithmetic on the Curve25519 Montgomery curve.

local fp = require "ccryptolib.internal.fp"
local ed = require "ccryptolib.internal.edwards25519"
local random = require "ccryptolib.random"

--- @class MtPoint A point class on Curve25519, in XZ coordinates.
--- @field [1] number[] The X coordinate.
--- @field [2] number[] The Z coordinate.

--- Doubles a point.
--- @param P1 MtPoint The point to double.
--- @return MtPoint P2 P1 + P1.
local function double(P1)
    local x1, z1 = P1[1], P1[2]
    local a = fp.add(x1, z1)
    local aa = fp.square(a)
    local b = fp.sub(x1, z1)
    local bb = fp.square(b)
    local c = fp.sub(aa, bb)
    local x3 = fp.mul(aa, bb)
    local z3 = fp.mul(c, fp.add(bb, fp.kmul(c, 121666)))
    return {x3, z3}
end

--- Computes differential addition on two points.
--- @param DP MtPoint P1 - P2.
--- @param P1 MtPoint The first point to add.
--- @param P2 MtPoint The second point to add.
--- @return MtPoint P3 P1 + P2.
local function dadd(DP, P1, P2)
    local dx, dz = DP[1], DP[2]
    local x1, z1 = P1[1], P1[2]
    local x2, z2 = P2[1], P2[2]
    local a = fp.add(x1, z1)
    local b = fp.sub(x1, z1)
    local c = fp.add(x2, z2)
    local d = fp.sub(x2, z2)
    local da = fp.mul(d, a)
    local cb = fp.mul(c, b)
    local x3 = fp.mul(dz, fp.square(fp.add(da, cb)))
    local z3 = fp.mul(dx, fp.square(fp.sub(da, cb)))
    return {x3, z3}
end

--- Performs a step on the Montgomery ladder.
--- @param DP MtPoint P1 - P2.
--- @param P1 MtPoint The first point.
--- @param P2 MtPoint The second point.
--- @return MtPoint P3 2A
--- @return MtPoint P4 A + B
local function step(DP, P1, P2)
    local dx, dz = DP[1], DP[2]
    local x1, z1 = P1[1], P1[2]
    local x2, z2 = P2[1], P2[2]
    local a = fp.add(x1, z1)
    local aa = fp.square(a)
    local b = fp.sub(x1, z1)
    local bb = fp.square(b)
    local e = fp.sub(aa, bb)
    local c = fp.add(x2, z2)
    local d = fp.sub(x2, z2)
    local da = fp.mul(d, a)
    local cb = fp.mul(c, b)
    local x4 = fp.mul(dz, fp.square(fp.add(da, cb)))
    local z4 = fp.mul(dx, fp.square(fp.sub(da, cb)))
    local x3 = fp.mul(aa, bb)
    local z3 = fp.mul(e, fp.add(bb, fp.kmul(e, 121666)))
    return {x3, z3}, {x4, z4}
end

local function ladder(DP, bits)
    local P = {fp.num(1), fp.num(0)}
    local Q = DP

    for i = #bits, 1, -1 do
        if bits[i] == 0 then
            P, Q = step(DP, P, Q)
        else
            Q, P = step(DP, Q, P)
        end
    end

    return P
end

--- Performs a scalar multiplication operation with multiplication by 8.
--- @param P MtPoint The base point.
--- @param bits number[] The scalar multiplier, in little-endian bits.
--- @return MtPoint product The product, multiplied by 8.
local function ladder8(P, bits)
    -- Randomize.
    local rf = fp.decode(random.random(32) --[[@as String32, length is given]])
    P = {fp.mul(P[1], rf), fp.mul(P[2], rf)}

    -- Multiply.
    return double(double(double(ladder(P, bits))))
end

--- Scales a point's coordinates.
--- @param P MtPoint The input point.
--- @return MtPoint Q The same point P, but with Z = 1.
local function scale(P)
    return {fp.mul(P[1], fp.invert(P[2])), fp.num(1)}
end

--- Encodes a scaled point.
--- @param P MtPoint The scaled point to encode.
--- @return string encoded P, encoded into a 32-byte string.
local function encode(P)
    return fp.encode(P[1])
end

--- Decodes a point.
--- @param str String32 A 32-byte encoded point.
--- @return MtPoint pt The decoded point.
local function decode(str)
    return {fp.decode(str), fp.num(1)}
end

--- Decodes an Edwards25519 encoded point into Curve25519, ignoring the sign.
---
--- There is a single exception: The identity point (0, 1), which gets mapped
--- into the 2-torsion point (0, 0), which isn't the identity of Curve25519.
---
--- @param str String32 A 32-byte encoded Edwards25519 point.
--- @return MtPoint pt The decoded point, mapped into Curve25519.
local function decodeEd(str)
    local y = fp.decode(str)
    local n = fp.carry(fp.add(fp.num(1), y))
    local d = fp.carry(fp.sub(fp.num(1), y))
    if fp.eqz(d) then
        return {fp.num(0), fp.num(1)}
    else
        return {n, d}
    end
end

--- Performs a scalar multiplication by the base point G.
--- @param bits number[] The scalar multiplier, in little-endian bits.
--- @return MtPoint product The product point.
local function mulG(bits)
    -- Multiply by G on Edwards25519.
    local P = ed.mulG(bits)

    -- Use the birational map to get the point on Curve25519.
    -- Never fails since G is in the large group, and the exponent is clamped.
    local Py, Pz = P[2], P[3]
    local Rx = fp.carry(fp.add(Py, Pz))
    local Rz = fp.carry(fp.sub(Pz, Py))

    return {Rx, Rz}
end

--- Computes a twofold product from a ruleset.
---
--- Returns nil if any of the results would be equal to the identity.
---
--- @param P MtPoint The base point.
--- @param ruleset __TYPE_TODO The ruleset generated by scalars m, n.
--- @return MtPoint? A [8m]P.
--- @return MtPoint? B [8n]P.
--- @return MtPoint? C [8m]P - [8n]P.
local function prac(P, ruleset)
    -- Randomize.
    local rf = fp.decode(random.random(32) --[[@as String32, length is given]])
    local A = {fp.mul(P[1], rf), fp.mul(P[2], rf)}

    -- Start the base at [8]P.
    local A = double(double(double(A)))

    -- Throw away small order points.
    if fp.eqz(A[2]) then return end

    -- Now e = d = gcd(m, n).
    -- Update A from [8]P to [8 * gcd(m, n)]P.
    A = ladder(A, ruleset[1])

    -- Reject rulesets where m = n.
    local rules = ruleset[2]
    if #rules == 0 then return end

    -- Evaluate the first rule.
    -- Since e = d, this means A - B = C = O. Differential addition fails when
    -- C = O, so we need to treat this case specially.
    -- Note that rules 0 and 1 never happen last, since the algorithm would stop
    -- one step earlier if they did:
    -- - If after rule 0 we had e = d, then (d, e) → (e, d) would also mean that
    --   e = d, so it stops one step earlier.
    -- - If after rule 1 we had e = d, then (d, e) → ((2d - e)/3, (2e - d)/3)
    --   would mean that (2d - e)/3 = (2e - d)/3, thus 2d - e = 2e - d, thus
    --   3d = 3e, thus d = e, so it stops one step earlier.
    local B, C
    local rule = rules[#rules]
    if rule == 2 then
        -- (A, B, C) ← (2A + B, B, 2A) = (3A, A, 2A)
        local A2 = double(A)
        A, B, C = dadd(A, A2, A), A, A2
    elseif rule == 3 or rule == 5 then
        -- (A, B, C) ← (A + B, B, A) = (2A, A, A)
        -- or (A, B, C) ← (2A, B, 2A - B) = (2A, A, A)
        A, B, C = double(A), A, A
    elseif rule == 6 then
        -- (A, B, C) ← (3A + 3B, B, 3A + 2B) = (6A, A, 5A)
        local A2 = double(A)
        local A3 = dadd(A, A2, A)
        A, B, C = double(A3), A, dadd(A, A3, A2)
    elseif rule == 7 then
        -- (A, B, C) ← (3A + 2B, B, 3A + B) = (5A, A, 4A)
        local A2 = double(A)
        local A3 = dadd(A, A2, A)
        local A4 = double(A2)
        A, B, C = dadd(A3, A4, A), A, A4
    elseif rule == 8 then
        -- (A, B, C) ← (3A + B, B, 3A) = (4A, A, 3A)
        local A2 = double(A)
        local A3 = dadd(A, A2, A)
        A, B, C = double(A2), A, A3
    else
        -- (A, B, C) ← (A, 2B, A - 2B) = (A, 2A, A)
        A, B, C = A, double(A), A
    end

    -- Evaluate the other rules.
    -- Let's assume addition is undefined here, this happens when A - B = O.
    -- Since A = [d]P and B = [e]P, A = B happens when:
    -- (1) P is on the large order base group and d ≡ e (mod q).
    -- (2) P is on the large order twist group and d ≡ e (mod q').
    -- (3) P is on a small order group.
    -- Case (3) never happens since we throw small order points away above.
    -- Since 0 ≤ {d, e} < q < q', a modular equivalence here means an integer
    -- equivalence. Therefore d = e.
    -- However, the ruleset stops when d = e, therefore the algorithm must have
    -- stopped earlier than when it did. Contradiction.
    -- Therefore, addition is always defined.
    -- Furthermore, the PRAC invariants mean that this product is the same as
    -- if the points were multiplied separately.
    for i = #rules - 1, 1, -1 do
        local rule = rules[i]
        if rule == 0 then
            -- (A, B, C) ← (B, A, B - A)
            A, B = B, A
        elseif rule == 1 then
            -- (A, B, C) ← (2A + B, A + 2B, A - B)
            local AB = dadd(C, A, B)
            A, B = dadd(B, AB, A), dadd(A, AB, B)
        elseif rule == 2 then
            -- (A, B, C) ← (2A + B, B, 2A)
            A, C = dadd(B, dadd(C, A, B), A), double(A)
        elseif rule == 3 then
            -- (A, B, C) ← (A + B, B, A)
            A, C = dadd(C, A, B), A
        elseif rule == 5 then
            -- (A, B, C) ← (2A, B, 2A - B)
            A, C = double(A), dadd(B, A, C)
        elseif rule == 6 then
            -- (A, B, C) ← (3A + 3B, B, 3A + 2B)
            local AB = dadd(C, A, B)
            local AABB = double(AB)
            A, C = dadd(AB, AABB, AB), dadd(dadd(A, AB, B), AABB, A)
        elseif rule == 7 then
            -- (A, B, C) ← (3A + 2B, B, 3A + B)
            local AB = dadd(C, A, B)
            local AAB = dadd(B, AB, A)
            A, C = dadd(A, AAB, AB), dadd(AB, AAB, A)
        elseif rule == 8 then
            -- (A, B, C) ← (3A + B, B, 3A)
            local AA = double(A)
            A, C = dadd(C, AA, dadd(C, A, B)), dadd(A, AA, A)
        else
            -- (A, B, C) ← (A, 2B, A - 2B)
            B, C = double(B), dadd(A, C, B)
        end
    end

    return A, B, C
end

return {
    G = {fp.num(9), fp.num(1)},
    dadd = dadd,
    scale = scale,
    encode = encode,
    decode = decode,
    decodeEd = decodeEd,
    ladder8 = ladder8,
    mulG = mulG,
    prac = prac,
}
]=],
["ccryptolib.internal.edwards25519"]=[=[--- Point arithmetic on the Edwards25519 Edwards curve.

local fp = require "ccryptolib.internal.fp"

local unpack = unpack or table.unpack

--- @class EdPoint A point on Edwards25519, in extended coordinates.
--- @field [1] number[] The X coordinate.
--- @field [2] number[] The Y coordinate.
--- @field [3] number[] The Z coordinate.
--- @field [4] number[] The T coordinate.

--- @class NsPoint A point on Edwards25519, in Niels' coordinates.
--- @field [1] number[] Preprocessed Y + X.
--- @field [2] number[] Preprocessed Y - X.
--- @field [3] number[] Preprocessed 2Z.
--- @field [4] number[] Preprocessed 2DT.

local D = fp.mul(fp.num(-121665), fp.invert(fp.num(121666)))
local K = fp.kmul(D, 2)

--- @type EdPoint
local O = {fp.num(0), fp.num(1), fp.num(1), fp.num(0)}
local G = nil

--- Doubles a point.
--- @param P1 EdPoint The point to double.
--- @return EdPoint P2 P1 + P1.
local function double(P1)
    -- Unsoundness: fp.sub(g, e), and fp.sub(d, i) break fp.sub's contract since
    -- it doesn't accept an fp2. Although not ideal, in practice this doesn't
    -- matter since fp.carry handles the larger sum.
    local P1x, P1y, P1z = unpack(P1)
    local a = fp.square(P1x)
    local b = fp.square(P1y)
    local c = fp.square(P1z)
    local d = fp.add(c, c)
    local e = fp.add(a, b)
    local f = fp.add(P1x, P1y)
    local g = fp.square(f)
    local h = fp.carry(fp.sub(g, e))
    local i = fp.sub(b, a)
    local j = fp.carry(fp.sub(d, i))
    local P3x = fp.mul(h, j)
    local P3y = fp.mul(i, e)
    local P3z = fp.mul(j, i)
    local P3t = fp.mul(h, e)
    return {P3x, P3y, P3z, P3t}
end

--- Adds two points.
--- @param P1 EdPoint The first summand point.
--- @param N2 NsPoint The second summand point.
--- @return EdPoint P3 P1 + P2, where N2 = niels(P2).
local function add(P1, N2)
    local P1x, P1y, P1z, P1t = unpack(P1)
    local N1p, N1m, N1z, N1t = unpack(N2)
    local a = fp.sub(P1y, P1x)
    local b = fp.mul(a, N1m)
    local c = fp.add(P1y, P1x)
    local d = fp.mul(c, N1p)
    local e = fp.mul(P1t, N1t)
    local f = fp.mul(P1z, N1z)
    local g = fp.sub(d, b)
    local h = fp.sub(f, e)
    local i = fp.add(f, e)
    local j = fp.add(d, b)
    local P3x = fp.mul(g, h)
    local P3y = fp.mul(i, j)
    local P3z = fp.mul(h, i)
    local P3t = fp.mul(g, j)
    return {P3x, P3y, P3z, P3t}
end

--- Subtracts one point from another.
--- @param P1 EdPoint The first summand point.
--- @param N2 NsPoint The second summand point.
--- @return EdPoint P3 P1 - P2, where N2 = niels(P2).
local function sub(P1, N2)
    local P1x, P1y, P1z, P1t = unpack(P1)
    local N1p, N1m, N1z, N1t = unpack(N2)
    local a = fp.sub(P1y, P1x)
    local b = fp.mul(a, N1p)
    local c = fp.add(P1y, P1x)
    local d = fp.mul(c, N1m)
    local e = fp.mul(P1t, N1t)
    local f = fp.mul(P1z, N1z)
    local g = fp.sub(d, b)
    local h = fp.add(f, e)
    local i = fp.sub(f, e)
    local j = fp.add(d, b)
    local P3x = fp.mul(g, h)
    local P3y = fp.mul(i, j)
    local P3z = fp.mul(h, i)
    local P3t = fp.mul(g, j)
    return {P3x, P3y, P3z, P3t}
end

--- Computes the Niels representation of a point.
--- @param P1 EdPoint The input point.
--- @return NsPoint N1 Niels' precomputation applied to P1.
local function niels(P1)
    local P1x, P1y, P1z, P1t = unpack(P1)
    local N3p = fp.add(P1y, P1x)
    local N3m = fp.sub(P1y, P1x)
    local N3z = fp.add(P1z, P1z)
    local N3t = fp.mul(P1t, K)
    return {N3p, N3m, N3z, N3t}
end

--- Scales a point.
--- @param P1 EdPoint The input point.
--- @return EdPoint P2 The same point as P1, but with Z = 1.
local function scale(P1)
    local P1x, P1y, P1z = unpack(P1)
    local zInv = fp.invert(P1z)
    local P3x = fp.mul(P1x, zInv)
    local P3y = fp.mul(P1y, zInv)
    local P3z = fp.num(1)
    local P3t = fp.mul(P3x, P3y)
    return {P3x, P3y, P3z, P3t}
end

--- Encodes a scaled point.
--- @param P1 EdPoint The scaled point to encode.
--- @return string out P1 encoded as a 32-byte string.
local function encode(P1)
    P1 = scale(P1)
    local P1x, P1y = unpack(P1)
    local y = fp.encode(P1y)
    local xBit = fp.canonicalize(P1x)[1] % 2
    return y:sub(1, -2) .. string.char(y:byte(-1) + xBit * 128)
end

--- Decodes a point.
--- @param str String32 A 32-byte encoded point.
--- @return EdPoint? P1 The decoded point, or nil if it isn't on the curve.
local function decode(str)
    local P3y = fp.decode(str)
    local a = fp.square(P3y)
    local b = fp.sub(a, fp.num(1))
    local c = fp.mul(a, D)
    local d = fp.add(c, fp.num(1))
    local P3x = fp.sqrtDiv(b, d)
    if not P3x then return nil end
    local xBit = fp.canonicalize(P3x)[1] % 2
    if xBit ~= bit32.extract(str:byte(-1), 7) then
        P3x = fp.carry(fp.neg(P3x))
    end
    local P3z = fp.num(1)
    local P3t = fp.mul(P3x, P3y)
    return {P3x, P3y, P3z, P3t}
end

G = decode("Xfffffffffffffffffffffffffffffff") --[[@as EdPoint, G is valid]]

--- Transforms little-endian bits into a signed radix-2^w form.
--- @param bits number[]
--- @param w number Log2 of the radix, must be at least 1.
--- @return number[]
local function signedRadixW(bits, w)
    -- TODO Find a more elegant way of doing this.
    local wPow = 2 ^ w
    local wPowh = wPow / 2
    local out = {}
    local acc = 0
    local mul = 1
    for i = 1, #bits do
        acc = acc + bits[i] * mul
        mul = mul * 2
        while i == #bits and acc > 0 or mul > wPow do
            local rem = acc % wPow
            if rem >= wPowh then rem = rem - wPow end
            acc = (acc - rem) / wPow
            mul = mul / wPow
            out[#out + 1] = rem
        end
    end
    return out
end

--- Computes a multiplication table for radix-2^w form multiplication.
--- @param P EdPoint The base point.
--- @param w number Log2 of the radix, must be at least 1.
--- @return NsPoint[][]
local function radixWTable(P, w)
    local out = {}
    for i = 1, math.ceil(256 / w) do
        local row = {niels(P)}
        for j = 2, 2 ^ w / 2 do
            P = add(P, row[1])
            row[j] = niels(P)
        end
        out[i] = row
        P = double(P)
    end
    return out
end

--- The radix logarithm of the precomputed table for G.
local G_W = 5

--- The precomputed multiplication table for G.
local G_TABLE = radixWTable(G, G_W)

--- Transforms little-endian bits into a signed radix-2^w non-adjacent form.
---
--- The returned array contains a 0 whenever a single doubling is needed, or an
--- odd integer when an addition with a multiple of the base is needed.
---
--- @param bits number[]
--- @param w number Log2 of the radix, must be at least 1.
--- @return number[]
local function wNaf(bits, w)
    -- TODO Find a more elegant way of doing this.
    local wPow = 2 ^ w
    local wPowh = wPow / 2
    local out = {}
    local acc = 0
    local mul = 1
    for i = 1, #bits do
        acc = acc + bits[i] * mul
        mul = mul * 2
        while i == #bits and acc > 0 or mul > wPow do
            if acc % 2 == 0 then
                acc = acc / 2
                mul = mul / 2
                out[#out + 1] = 0
            else
                local rem = acc % wPow
                if rem >= wPowh then rem = rem - wPow end
                acc = acc - rem
                out[#out + 1] = rem
            end
        end
    end
    while out[#out] == 0 do out[#out] = nil end
    return out
end

--- Computes a multiplication table for wNAF form multiplication.
--- @param P EdPoint The base point.
--- @param w number Log2 of the radix, must be at least 1.
--- @return NsPoint[]
local function WNAFTable(P, w)
    local dP = double(P)
    local out = {niels(P)}
    for i = 3, 2 ^ w, 2 do
        out[i] = niels(add(dP, out[i - 2]))
    end
    return out
end

--- Performs a scalar multiplication by the base point G.
--- @param bits number[] The scalar multiplicand little-endian bits.
--- @return EdPoint
local function mulG(bits)
    local sw = signedRadixW(bits, G_W)
    local R = O
    for i = 1, #sw do
        local b = sw[i]
        if b > 0 then
            R = add(R, G_TABLE[i][b])
        elseif b < 0 then
            R = sub(R, G_TABLE[i][-b])
        end
    end
    return R
end

--- Performs a scalar multiplication operation.
--- @param P EdPoint The base point.
--- @param bits number[] The scalar multiplicand little-endian bits.
--- @return EdPoint
local function mul(P, bits)
    local naf = wNaf(bits, 5)
    local tbl = WNAFTable(P, 5)
    local R = O
    for i = #naf, 1, -1 do
        local b = naf[i]
        if b == 0 then
            R = double(R)
        elseif b > 0 then
            R = add(R, tbl[b])
        else
            R = sub(R, tbl[-b])
        end
    end
    return R
end

return {
    double = double,
    add = add,
    sub = sub,
    niels = niels,
    scale = scale,
    encode = encode,
    decode = decode,
    mulG = mulG,
    mul = mul,
}
]=],
["ccryptolib.internal.fp"]=[=[--- Arithmetic on Curve25519's base field.

local packing = require "ccryptolib.internal.packing"

local unpack = unpack or table.unpack
local ufp, fmtfp = packing.compileUnpack("<I3I3I2I3I3I2I3I3I2I3I3I2")

--- @class Fq An element of the field of integers modulo 2²⁵⁵ - 19.

--- @class FpR2: Fq An Fp element with limbs inside twice the standard range.

--- @class FpR1: FpR2 An Fp element with limbs inside the standard range. See
--- the Curve25519 polynomial representation for more info around this.

--- The modular square root of -1.
--- @type FpR1
local I = {
    0958640 * 2 ^ 0,
    0826664 * 2 ^ 22,
    1613251 * 2 ^ 43,
    1041528 * 2 ^ 64,
    0013673 * 2 ^ 85,
    0387171 * 2 ^ 107,
    1824679 * 2 ^ 128,
    0313839 * 2 ^ 149,
    0709440 * 2 ^ 170,
    0122635 * 2 ^ 192,
    0262782 * 2 ^ 213,
    0712905 * 2 ^ 234,
}

--- Converts a Lua number to an element.
--- @param n number A number n in [0..2²²).
--- @return FpR1 out The number as an element.
local function num(n)
    return {n, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
end

--- Negates an element.
--- @param a FpR1
--- @return FpR1 out -a.
local function neg(a)
    local a00, a01, a02, a03, a04, a05, a06, a07, a08, a09, a10, a11 = unpack(a)
    return {
        -a00,
        -a01,
        -a02,
        -a03,
        -a04,
        -a05,
        -a06,
        -a07,
        -a08,
        -a09,
        -a10,
        -a11,
    }
end

--- Adds two elements.
--- @param a FpR1
--- @param b FpR1
--- @return FpR2 out a + b.
local function add(a, b)
    local a00, a01, a02, a03, a04, a05, a06, a07, a08, a09, a10, a11 = unpack(a)
    local b00, b01, b02, b03, b04, b05, b06, b07, b08, b09, b10, b11 = unpack(b)
    return {
        a00 + b00,
        a01 + b01,
        a02 + b02,
        a03 + b03,
        a04 + b04,
        a05 + b05,
        a06 + b06,
        a07 + b07,
        a08 + b08,
        a09 + b09,
        a10 + b10,
        a11 + b11,
    }
end

--- Subtracts an element from another.
--- @param a FpR1
--- @param b FpR1
--- @return FpR2 out a - b.
local function sub(a, b)
    local a00, a01, a02, a03, a04, a05, a06, a07, a08, a09, a10, a11 = unpack(a)
    local b00, b01, b02, b03, b04, b05, b06, b07, b08, b09, b10, b11 = unpack(b)
    return {
        a00 - b00,
        a01 - b01,
        a02 - b02,
        a03 - b03,
        a04 - b04,
        a05 - b05,
        a06 - b06,
        a07 - b07,
        a08 - b08,
        a09 - b09,
        a10 - b10,
        a11 - b11,
    }
end

--- Carries an element. Also performs a small reduction modulo p.
--- @param a FpR2 The element to carry.
--- @return FpR1 out The same element as a but in a tighter range.
local function carry(a)
    local a00, a01, a02, a03, a04, a05, a06, a07, a08, a09, a10, a11 = unpack(a)
    local c00, c01, c02, c03, c04, c05, c06, c07, c08, c09, c10, c11

    c11 = a11 + 3 * 2 ^ 306 - 3 * 2 ^ 306  a00 = a00 + 19 / 2 ^ 255 * c11

    c00 = a00 + 3 * 2 ^ 73  - 3 * 2 ^ 73   a01 = a01 + c00
    c01 = a01 + 3 * 2 ^ 94  - 3 * 2 ^ 94   a02 = a02 + c01
    c02 = a02 + 3 * 2 ^ 115 - 3 * 2 ^ 115  a03 = a03 + c02
    c03 = a03 + 3 * 2 ^ 136 - 3 * 2 ^ 136  a04 = a04 + c03
    c04 = a04 + 3 * 2 ^ 158 - 3 * 2 ^ 158  a05 = a05 + c04
    c05 = a05 + 3 * 2 ^ 179 - 3 * 2 ^ 179  a06 = a06 + c05
    c06 = a06 + 3 * 2 ^ 200 - 3 * 2 ^ 200  a07 = a07 + c06
    c07 = a07 + 3 * 2 ^ 221 - 3 * 2 ^ 221  a08 = a08 + c07
    c08 = a08 + 3 * 2 ^ 243 - 3 * 2 ^ 243  a09 = a09 + c08
    c09 = a09 + 3 * 2 ^ 264 - 3 * 2 ^ 264  a10 = a10 + c09
    c10 = a10 + 3 * 2 ^ 285 - 3 * 2 ^ 285  a11 = a11 - c11 + c10

    c11 = a11 + 3 * 2 ^ 306 - 3 * 2 ^ 306

    return {
        a00 - c00 + 19 / 2 ^ 255 * c11,
        a01 - c01,
        a02 - c02,
        a03 - c03,
        a04 - c04,
        a05 - c05,
        a06 - c06,
        a07 - c07,
        a08 - c08,
        a09 - c09,
        a10 - c10,
        a11 - c11,
    }
end

--- Returns the canoncal representative of a modp number.
---
--- Some elements can be represented by two different arrays of floats. This
--- returns the canonical element of the represented equivalence class. We
--- define an element as canonical if it's the smallest nonnegative number in
--- its class.
---
--- @param a FpR2
--- @return FpR1 out A canonical element a' ≡ a (mod p).
local function canonicalize(a)
    local a00, a01, a02, a03, a04, a05, a06, a07, a08, a09, a10, a11 = unpack(a)
    local c00, c01, c02, c03, c04, c05, c06, c07, c08, c09, c10, c11

    -- Perform an euclidean reduction.
    -- TODO Range check.
    c00 = a00 % 2 ^ 22   a01 = a00 - c00 + a01
    c01 = a01 % 2 ^ 43   a02 = a01 - c01 + a02
    c02 = a02 % 2 ^ 64   a03 = a02 - c02 + a03
    c03 = a03 % 2 ^ 85   a04 = a03 - c03 + a04
    c04 = a04 % 2 ^ 107  a05 = a04 - c04 + a05
    c05 = a05 % 2 ^ 128  a06 = a05 - c05 + a06
    c06 = a06 % 2 ^ 149  a07 = a06 - c06 + a07
    c07 = a07 % 2 ^ 170  a08 = a07 - c07 + a08
    c08 = a08 % 2 ^ 192  a09 = a08 - c08 + a09
    c09 = a09 % 2 ^ 213  a10 = a09 - c09 + a10
    c10 = a10 % 2 ^ 234  a11 = a10 - c10 + a11
    c11 = a11 % 2 ^ 255  c00 = c00 + 19 / 2 ^ 255 * (a11 - c11)

    -- Canonicalize.
    if      c11 / 2 ^ 234 == 2 ^ 21 - 1
        and c10 / 2 ^ 213 == 2 ^ 21 - 1
        and c09 / 2 ^ 192 == 2 ^ 21 - 1
        and c08 / 2 ^ 170 == 2 ^ 22 - 1
        and c07 / 2 ^ 149 == 2 ^ 21 - 1
        and c06 / 2 ^ 128 == 2 ^ 21 - 1
        and c05 / 2 ^ 107 == 2 ^ 21 - 1
        and c04 / 2 ^ 85  == 2 ^ 22 - 1
        and c03 / 2 ^ 64  == 2 ^ 21 - 1
        and c02 / 2 ^ 43  == 2 ^ 21 - 1
        and c01 / 2 ^ 22  == 2 ^ 21 - 1
        and c00 >= 2 ^ 22 - 19
    then
        return {19 - 2 ^ 22 + c00, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
    else
        return {c00, c01, c02, c03, c04, c05, c06, c07, c08, c09, c10, c11}
    end
end

--- Returns whether two elements are the same.
--- @param a FpR1
--- @param b FpR1
--- @return boolean eq Whether a ≡ b (mod p).
local function eq(a, b)
    local c = canonicalize(sub(a, b))
    for i = 1, 12 do if c[i] ~= 0 then return false end end
    return true
end

--- Multiplies two elements.
--- @param a FpR2
--- @param b FpR2
--- @return FpR1 c An element such that c ≡ a × b (mod p).
local function mul(a, b)
    local a00, a01, a02, a03, a04, a05, a06, a07, a08, a09, a10, a11 = unpack(a)
    local b00, b01, b02, b03, b04, b05, b06, b07, b08, b09, b10, b11 = unpack(b)
    local c00, c01, c02, c03, c04, c05, c06, c07, c08, c09, c10, c11

    -- Multiply high half into c00..c11.
    c00 = a11 * b01
        + a10 * b02
        + a09 * b03
        + a08 * b04
        + a07 * b05
        + a06 * b06
        + a05 * b07
        + a04 * b08
        + a03 * b09
        + a02 * b10
        + a01 * b11
    c01 = a11 * b02
        + a10 * b03
        + a09 * b04
        + a08 * b05
        + a07 * b06
        + a06 * b07
        + a05 * b08
        + a04 * b09
        + a03 * b10
        + a02 * b11
    c02 = a11 * b03
        + a10 * b04
        + a09 * b05
        + a08 * b06
        + a07 * b07
        + a06 * b08
        + a05 * b09
        + a04 * b10
        + a03 * b11
    c03 = a11 * b04
        + a10 * b05
        + a09 * b06
        + a08 * b07
        + a07 * b08
        + a06 * b09
        + a05 * b10
        + a04 * b11
    c04 = a11 * b05
        + a10 * b06
        + a09 * b07
        + a08 * b08
        + a07 * b09
        + a06 * b10
        + a05 * b11
    c05 = a11 * b06
        + a10 * b07
        + a09 * b08
        + a08 * b09
        + a07 * b10
        + a06 * b11
    c06 = a11 * b07
        + a10 * b08
        + a09 * b09
        + a08 * b10
        + a07 * b11
    c07 = a11 * b08
        + a10 * b09
        + a09 * b10
        + a08 * b11
    c08 = a11 * b09
        + a10 * b10
        + a09 * b11
    c09 = a11 * b10
        + a10 * b11
    c10 = a11 * b11

    -- Multiply low half with reduction into c00..c11.
    c00 = c00 * (19 / 2 ^ 255)
        + a00 * b00
    c01 = c01 * (19 / 2 ^ 255)
        + a01 * b00
        + a00 * b01
    c02 = c02 * (19 / 2 ^ 255)
        + a02 * b00
        + a01 * b01
        + a00 * b02
    c03 = c03 * (19 / 2 ^ 255)
        + a03 * b00
        + a02 * b01
        + a01 * b02
        + a00 * b03
    c04 = c04 * (19 / 2 ^ 255)
        + a04 * b00
        + a03 * b01
        + a02 * b02
        + a01 * b03
        + a00 * b04
    c05 = c05 * (19 / 2 ^ 255)
        + a05 * b00
        + a04 * b01
        + a03 * b02
        + a02 * b03
        + a01 * b04
        + a00 * b05
    c06 = c06 * (19 / 2 ^ 255)
        + a06 * b00
        + a05 * b01
        + a04 * b02
        + a03 * b03
        + a02 * b04
        + a01 * b05
        + a00 * b06
    c07 = c07 * (19 / 2 ^ 255)
        + a07 * b00
        + a06 * b01
        + a05 * b02
        + a04 * b03
        + a03 * b04
        + a02 * b05
        + a01 * b06
        + a00 * b07
    c08 = c08 * (19 / 2 ^ 255)
        + a08 * b00
        + a07 * b01
        + a06 * b02
        + a05 * b03
        + a04 * b04
        + a03 * b05
        + a02 * b06
        + a01 * b07
        + a00 * b08
    c09 = c09 * (19 / 2 ^ 255)
        + a09 * b00
        + a08 * b01
        + a07 * b02
        + a06 * b03
        + a05 * b04
        + a04 * b05
        + a03 * b06
        + a02 * b07
        + a01 * b08
        + a00 * b09
    c10 = c10 * (19 / 2 ^ 255)
        + a10 * b00
        + a09 * b01
        + a08 * b02
        + a07 * b03
        + a06 * b04
        + a05 * b05
        + a04 * b06
        + a03 * b07
        + a02 * b08
        + a01 * b09
        + a00 * b10
    c11 = a11 * b00
        + a10 * b01
        + a09 * b02
        + a08 * b03
        + a07 * b04
        + a06 * b05
        + a05 * b06
        + a04 * b07
        + a03 * b08
        + a02 * b09
        + a01 * b10
        + a00 * b11

    -- Carry and reduce.
    a10 = c10 + 3 * 2 ^ 285 - 3 * 2 ^ 285  c11 = c11 + a10
    a11 = c11 + 3 * 2 ^ 306 - 3 * 2 ^ 306  c00 = c00 + 19 / 2 ^ 255 * a11

    a00 = c00 + 3 * 2 ^ 73  - 3 * 2 ^ 73   c01 = c01 + a00
    a01 = c01 + 3 * 2 ^ 94  - 3 * 2 ^ 94   c02 = c02 + a01
    a02 = c02 + 3 * 2 ^ 115 - 3 * 2 ^ 115  c03 = c03 + a02
    a03 = c03 + 3 * 2 ^ 136 - 3 * 2 ^ 136  c04 = c04 + a03
    a04 = c04 + 3 * 2 ^ 158 - 3 * 2 ^ 158  c05 = c05 + a04
    a05 = c05 + 3 * 2 ^ 179 - 3 * 2 ^ 179  c06 = c06 + a05
    a06 = c06 + 3 * 2 ^ 200 - 3 * 2 ^ 200  c07 = c07 + a06
    a07 = c07 + 3 * 2 ^ 221 - 3 * 2 ^ 221  c08 = c08 + a07
    a08 = c08 + 3 * 2 ^ 243 - 3 * 2 ^ 243  c09 = c09 + a08
    a09 = c09 + 3 * 2 ^ 264 - 3 * 2 ^ 264  c10 = c10 - a10 + a09
    a10 = c10 + 3 * 2 ^ 285 - 3 * 2 ^ 285  c11 = c11 - a11 + a10

    a11 = c11 + 3 * 2 ^ 306 - 3 * 2 ^ 306

    return {
        c00 - a00 + 19 / 2 ^ 255 * a11,
        c01 - a01,
        c02 - a02,
        c03 - a03,
        c04 - a04,
        c05 - a05,
        c06 - a06,
        c07 - a07,
        c08 - a08,
        c09 - a09,
        c10 - a10,
        c11 - a11,
    }
end

--- Squares an element.
--- @param a FpR2
--- @return FpR1 b An element such that b ≡ a² (mod p).
local function square(a)
    local a00, a01, a02, a03, a04, a05, a06, a07, a08, a09, a10, a11 = unpack(a)
    local d00, d01, d02, d03, d04, d05, d06, d07, d08, d09, d10
    local c00, c01, c02, c03, c04, c05, c06, c07, c08, c09, c10, c11

    -- Compute 2a.
    d00 = a00 + a00
    d01 = a01 + a01
    d02 = a02 + a02
    d03 = a03 + a03
    d04 = a04 + a04
    d05 = a05 + a05
    d06 = a06 + a06
    d07 = a07 + a07
    d08 = a08 + a08
    d09 = a09 + a09
    d10 = a10 + a10

    -- Multiply high half into c00..c11.
    c00 = a11 * d01
        + a10 * d02
        + a09 * d03
        + a08 * d04
        + a07 * d05
        + a06 * a06
    c01 = a11 * d02
        + a10 * d03
        + a09 * d04
        + a08 * d05
        + a07 * d06
    c02 = a11 * d03
        + a10 * d04
        + a09 * d05
        + a08 * d06
        + a07 * a07
    c03 = a11 * d04
        + a10 * d05
        + a09 * d06
        + a08 * d07
    c04 = a11 * d05
        + a10 * d06
        + a09 * d07
        + a08 * a08
    c05 = a11 * d06
        + a10 * d07
        + a09 * d08
    c06 = a11 * d07
        + a10 * d08
        + a09 * a09
    c07 = a11 * d08
        + a10 * d09
    c08 = a11 * d09
        + a10 * a10
    c09 = a11 * d10
    c10 = a11 * a11

    -- Multiply low half with reduction into c00..c11.
    c00 = c00 * (19 / 2 ^ 255)
        + a00 * a00
    c01 = c01 * (19 / 2 ^ 255)
        + a01 * d00
    c02 = c02 * (19 / 2 ^ 255)
        + a02 * d00
        + a01 * a01
    c03 = c03 * (19 / 2 ^ 255)
        + a03 * d00
        + a02 * d01
    c04 = c04 * (19 / 2 ^ 255)
        + a04 * d00
        + a03 * d01
        + a02 * a02
    c05 = c05 * (19 / 2 ^ 255)
        + a05 * d00
        + a04 * d01
        + a03 * d02
    c06 = c06 * (19 / 2 ^ 255)
        + a06 * d00
        + a05 * d01
        + a04 * d02
        + a03 * a03
    c07 = c07 * (19 / 2 ^ 255)
        + a07 * d00
        + a06 * d01
        + a05 * d02
        + a04 * d03
    c08 = c08 * (19 / 2 ^ 255)
        + a08 * d00
        + a07 * d01
        + a06 * d02
        + a05 * d03
        + a04 * a04
    c09 = c09 * (19 / 2 ^ 255)
        + a09 * d00
        + a08 * d01
        + a07 * d02
        + a06 * d03
        + a05 * d04
    c10 = c10 * (19 / 2 ^ 255)
        + a10 * d00
        + a09 * d01
        + a08 * d02
        + a07 * d03
        + a06 * d04
        + a05 * a05
    c11 = a11 * d00
        + a10 * d01
        + a09 * d02
        + a08 * d03
        + a07 * d04
        + a06 * d05

    -- Carry and reduce.
    a10 = c10 + 3 * 2 ^ 285 - 3 * 2 ^ 285  c11 = c11 + a10
    a11 = c11 + 3 * 2 ^ 306 - 3 * 2 ^ 306  c00 = c00 + 19 / 2 ^ 255 * a11

    a00 = c00 + 3 * 2 ^ 73  - 3 * 2 ^ 73   c01 = c01 + a00
    a01 = c01 + 3 * 2 ^ 94  - 3 * 2 ^ 94   c02 = c02 + a01
    a02 = c02 + 3 * 2 ^ 115 - 3 * 2 ^ 115  c03 = c03 + a02
    a03 = c03 + 3 * 2 ^ 136 - 3 * 2 ^ 136  c04 = c04 + a03
    a04 = c04 + 3 * 2 ^ 158 - 3 * 2 ^ 158  c05 = c05 + a04
    a05 = c05 + 3 * 2 ^ 179 - 3 * 2 ^ 179  c06 = c06 + a05
    a06 = c06 + 3 * 2 ^ 200 - 3 * 2 ^ 200  c07 = c07 + a06
    a07 = c07 + 3 * 2 ^ 221 - 3 * 2 ^ 221  c08 = c08 + a07
    a08 = c08 + 3 * 2 ^ 243 - 3 * 2 ^ 243  c09 = c09 + a08
    a09 = c09 + 3 * 2 ^ 264 - 3 * 2 ^ 264  c10 = c10 - a10 + a09
    a10 = c10 + 3 * 2 ^ 285 - 3 * 2 ^ 285  c11 = c11 - a11 + a10

    a11 = c11 + 3 * 2 ^ 306 - 3 * 2 ^ 306

    return {
        c00 - a00 + 19 / 2 ^ 255 * a11,
        c01 - a01,
        c02 - a02,
        c03 - a03,
        c04 - a04,
        c05 - a05,
        c06 - a06,
        c07 - a07,
        c08 - a08,
        c09 - a09,
        c10 - a10,
        c11 - a11,
    }
end

--- Multiplies an element by a number.
--- @param a FpR2
--- @param k number A number in [0..2²²).
--- @return FpR1 c An element such that c ≡ a × k (mod p).
local function kmul(a, k)
    local a00, a01, a02, a03, a04, a05, a06, a07, a08, a09, a10, a11 = unpack(a)
    local c00, c01, c02, c03, c04, c05, c06, c07, c08, c09, c10, c11

    -- TODO Range check.
    a00 = a00 * k
    a01 = a01 * k
    a02 = a02 * k
    a03 = a03 * k
    a04 = a04 * k
    a05 = a05 * k
    a06 = a06 * k
    a07 = a07 * k
    a08 = a08 * k
    a09 = a09 * k
    a10 = a10 * k
    a11 = a11 * k

    c11 = a11 + 3 * 2 ^ 306 - 3 * 2 ^ 306  a00 = a00 + 19 / 2 ^ 255 * c11

    c00 = a00 + 3 * 2 ^ 73  - 3 * 2 ^ 73   a01 = a01 + c00
    c01 = a01 + 3 * 2 ^ 94  - 3 * 2 ^ 94   a02 = a02 + c01
    c02 = a02 + 3 * 2 ^ 115 - 3 * 2 ^ 115  a03 = a03 + c02
    c03 = a03 + 3 * 2 ^ 136 - 3 * 2 ^ 136  a04 = a04 + c03
    c04 = a04 + 3 * 2 ^ 158 - 3 * 2 ^ 158  a05 = a05 + c04
    c05 = a05 + 3 * 2 ^ 179 - 3 * 2 ^ 179  a06 = a06 + c05
    c06 = a06 + 3 * 2 ^ 200 - 3 * 2 ^ 200  a07 = a07 + c06
    c07 = a07 + 3 * 2 ^ 221 - 3 * 2 ^ 221  a08 = a08 + c07
    c08 = a08 + 3 * 2 ^ 243 - 3 * 2 ^ 243  a09 = a09 + c08
    c09 = a09 + 3 * 2 ^ 264 - 3 * 2 ^ 264  a10 = a10 + c09
    c10 = a10 + 3 * 2 ^ 285 - 3 * 2 ^ 285  a11 = a11 - c11 + c10

    c11 = a11 + 3 * 2 ^ 306 - 3 * 2 ^ 306

    return {
        a00 - c00 + 19 / 2 ^ 255 * c11,
        a01 - c01,
        a02 - c02,
        a03 - c03,
        a04 - c04,
        a05 - c05,
        a06 - c06,
        a07 - c07,
        a08 - c08,
        a09 - c09,
        a10 - c10,
        a11 - c11
    }
end

--- Squares an element n times.
--- @param a FpR2
--- @param n number The number of times to square a.
--- @return FpR1 c A number c such that c ≡ a ^ 2 ^ n (mod p).
local function nsquare(a, n)
    for _ = 1, n do a = square(a) end
    return a
end

--- Computes the inverse of an element.
---
--- Performance: 11 multiplications and 252 squarings.
---
--- @param a FpR2
--- @return FpR1 c An element such that c ≡ a⁻¹ (mod p), or 0 if c doesn't exist.
local function invert(a)
    local a2 = square(a)
    local a9 = mul(a, nsquare(a2, 2))
    local a11 = mul(a9, a2)

    local x5 = mul(square(a11), a9)
    local x10 = mul(nsquare(x5, 5), x5)
    local x20 = mul(nsquare(x10, 10), x10)
    local x40 = mul(nsquare(x20, 20), x20)
    local x50 = mul(nsquare(x40, 10), x10)
    local x100 = mul(nsquare(x50, 50), x50)
    local x200 = mul(nsquare(x100, 100), x100)
    local x250 = mul(nsquare(x200, 50), x50)

    return mul(nsquare(x250, 5), a11)
end

--- Returns an element x that satisfies vx² = u.
---
--- Note that when v = 0, the returned element can take any value.
---
--- @param u FpR2
--- @param v FpR2
--- @return FpR1? x An element such that vx² ≡ u (mod p), if it exists.
local function sqrtDiv(u, v)
    u = carry(u)

    local v2 = square(v)
    local v3 = mul(v, v2)
    local v6 = square(v3)
    local v7 = mul(v, v6)
    local uv7 = mul(u, v7)

    local x2 = mul(square(uv7), uv7)
    local x4 = mul(nsquare(x2, 2), x2)
    local x8 = mul(nsquare(x4, 4), x4)
    local x16 = mul(nsquare(x8, 8), x8)
    local x18 = mul(nsquare(x16, 2), x2)
    local x32 = mul(nsquare(x16, 16), x16)
    local x50 = mul(nsquare(x32, 18), x18)
    local x100 = mul(nsquare(x50, 50), x50)
    local x200 = mul(nsquare(x100, 100), x100)
    local x250 = mul(nsquare(x200, 50), x50)
    local pr = mul(nsquare(x250, 2), uv7)

    local uv3 = mul(u, v3)
    local b = mul(uv3, pr)
    local b2 = square(b)
    local vb2 = mul(v, b2)

    if not eq(vb2, u) then
        -- Found sqrt(-u/v), multiply by i.
        b = mul(b, I)
        b2 = square(b)
        vb2 = mul(v, b2)
    end

    if eq(vb2, u) then
        return b
    else
        return nil
    end
end

--- @class String32: string A string with length equal to 32 bytes.

--- Encodes an element in little-endian.
--- @param a FpR1
--- @return String32 out The 32-byte canonical encoding of a.
local function encode(a)
    a = canonicalize(a)
    local a00, a01, a02, a03, a04, a05, a06, a07, a08, a09, a10, a11 = unpack(a)

    local bytes = {}
    local acc = a00

    local function putBytes(n)
        for _ = 1, n do
            local byte = acc % 256
            bytes[#bytes + 1] = byte
            acc = (acc - byte) / 256
        end
    end

    putBytes(2) acc = acc + a01 / 2 ^ 16
    putBytes(3) acc = acc + a02 / 2 ^ 40
    putBytes(3) acc = acc + a03 / 2 ^ 64
    putBytes(2) acc = acc + a04 / 2 ^ 80
    putBytes(3) acc = acc + a05 / 2 ^ 104
    putBytes(3) acc = acc + a06 / 2 ^ 128
    putBytes(2) acc = acc + a07 / 2 ^ 144
    putBytes(3) acc = acc + a08 / 2 ^ 168
    putBytes(3) acc = acc + a09 / 2 ^ 192
    putBytes(2) acc = acc + a10 / 2 ^ 208
    putBytes(3) acc = acc + a11 / 2 ^ 232
    putBytes(3)

    return string.char(unpack(bytes)) --[[@as String32, putBytes sums to 32]]
end

--- Decodes an element in little-endian.
--- @param b String32 A 32-byte string, the most-significant bit is discarded.
--- @return FpR1 out The decoded element. It may not be canonical.
local function decode(b)
    local w00, w01, w02, w03, w04, w05, w06, w07, w08, w09, w10, w11 =
        ufp(fmtfp, b, 1)

    w11 = w11 % 2 ^ 15

    return carry {
        w00,
        w01 * 2 ^ 24,
        w02 * 2 ^ 48,
        w03 * 2 ^ 64,
        w04 * 2 ^ 88,
        w05 * 2 ^ 112,
        w06 * 2 ^ 128,
        w07 * 2 ^ 152,
        w08 * 2 ^ 176,
        w09 * 2 ^ 192,
        w10 * 2 ^ 216,
        w11 * 2 ^ 240,
    }
end

--- Checks if the given element is equal to 0.
--- @param a FpR2
--- @return boolean eqz Whether a ≡ 0 (mod p).
local function eqz(a)
    local c = canonicalize(a)
    local c00, c01, c02, c03, c04, c05, c06, c07, c08, c09, c10, c11 = unpack(c)
    return c00 + c01 + c02 + c03 + c04 + c05 + c06 + c07 + c08 + c09 + c10 + c11
        == 0
end

return {
    num = num,
    neg = neg,
    add = add,
    sub = sub,
    kmul = kmul,
    mul = mul,
    canonicalize = canonicalize,
    square = square,
    carry = carry,
    invert = invert,
    sqrtDiv = sqrtDiv,
    encode = encode,
    decode = decode,
    eqz = eqz,
}
]=],
["ccryptolib.internal.fq"]=[=[--- Arithmetic on Curve25519's scalar field.

local mp = require "ccryptolib.internal.mp"
local util = require "ccryptolib.internal.util"
local packing = require "ccryptolib.internal.packing"

local unpack = unpack or table.unpack
local pfq, fmtfq = packing.compilePack("<I3I3I3I3I3I3I3I3I3I3I2")
local ufq = packing.compileUnpack(fmtfq)
local ufql, fmtfql = packing.compileUnpack("<I3I3I3I3I3I3I3I3I3I3I3")
local ufqh, fmtfqh = packing.compileUnpack("<I3I3I3I3I3I3I3I3I3I3I1")

--- The scalar field's order, q = 2²⁵² + 27742317777372353535851937790883648493.
local Q = {
    16110573,
    06494812,
    14047250,
    10680220,
    14612958,
    00000020,
    00000000,
    00000000,
    00000000,
    00000000,
    00004096,
}

--- The first Montgomery precomputed constant, -q⁻¹ mod 2²⁶⁴.
local T0 = {
    05537307,
    01942290,
    16765621,
    16628356,
    10618610,
    07072433,
    03735459,
    01369940,
    15276086,
    13038191,
    13409718,
}

--- The second Montgomery precomputed constant, 2⁵²⁸ mod q.
local T1 = {
    11711996,
    01747860,
    08326961,
    03814718,
    01859974,
    13327461,
    16105061,
    07590423,
    04050668,
    08138906,
    00000283,
}

local T8 = {
    5110253,
    3039345,
    2503500,
    11779568,
    15416472,
    16766550,
    16777215,
    16777215,
    16777215,
    16777215,
    4095,
}

local ZERO = mp.num(0)

--- Reduces a number modulo q.
--
-- @tparam {number...} a A number a < 2q as 11 limbs in [0..2²⁵).
-- @treturn {number...} a mod q as 11 limbs in [0..2²⁴).
--
local function reduce(a)
    local c = mp.sub(a, Q)
    local out, overflow = mp.carry(c)

    -- Return carry(a) if a < q.
    if overflow < 0 then return (mp.carry(a)) end

    -- c >= q means c - q >= 0.
    -- Since q < 2²⁸⁸, c < 2q means c - q < q < 2²⁸⁸.
    -- c's limbs fit in (-2²⁶..2²⁶), since subtraction adds at most one bit.
    return out -- cc < q implies that the carry number is 0.
end

--- Adds two scalars mod q.
--
-- If the two operands are in Montgomery form, returns the correct result also
-- in Montgomery form, since (2²⁶⁴ × a) + (2²⁶⁴ × b) ≡ 2²⁶⁴ × (a + b) (mod q).
--
-- @tparam {number...} a A number a < q as 11 limbs in [0..2²⁴).
-- @tparam {number...} b A number b < q as 11 limbs in [0..2²⁴).
-- @treturn {number...} a + b mod q as 11 limbs in [0..2²⁴).
--
local function add(a, b)
    return reduce(mp.add(a, b))
end

--- Negates a scalar mod q.
--
-- @tparam {number...} a A number a < q as 11 limbs in [0..2²⁴).
-- @treturn {number...} -a mod q as 11 limbs in [0..2²⁴).
--
local function neg(a)
    return reduce(mp.sub(Q, a))
end

--- Subtracts scalars mod q.
--
-- If the two operands are in Montgomery form, returns the correct result also
-- in Montgomery form, since (2²⁶⁴ × a) - (2²⁶⁴ × b) ≡ 2²⁶⁴ × (a - b) (mod q).
--
-- @tparam {number...} a A number a < q as 11 limbs in [0..2²⁴).
-- @tparam {number...} b A number b < q as 11 limbs in [0..2²⁴).
-- @treturn {number...} a - b mod q as 11 limbs in [0..2²⁴).
--
local function sub(a, b)
    return add(a, neg(b))
end

--- Given two scalars a and b, computes 2⁻²⁶⁴ × a × b mod q.
--
-- @tparam {number...} a A number a as 11 limbs in [0..2²⁴).
-- @tparam {number...} b A number b < q as 11 limbs in [0..2²⁴).
-- @treturn {number...} 2⁻²⁶⁴ × a × b mod q as 11 limbs in [0..2²⁴).
--
local function mul(a, b)
    local t0, t1 = mp.mul(a, b)
    local mq0, mq1 = mp.mul(mp.lmul(t0, T0), Q)
    local _, s1 = mp.dwadd(t0, t1, mq0, mq1)
    return reduce(s1)
end

--- Converts a scalar into Montgomery form.
--
-- @tparam {number...} a A number a as 11 limbs in [0..2²⁴).
-- @treturn {number...} 2²⁶⁴ × a mod q as 11 limbs in [0..2²⁴).
--
local function montgomery(a)
    -- 0 ≤ a < 2²⁶⁴ and 0 ≤ T1 < q.
    return mul(a, T1)
end

--- Converts a scalar from Montgomery form.
--
-- @tparam {number...} a A number a < q as 11 limbs in [0..2²⁴).
-- @treturn {number...} 2⁻²⁶⁴ × a mod q as 11 limbs in [0..2²⁴).
--
local function demontgomery(a)
    -- It's REDC all over again except b is 1.
    local mq0, mq1 = mp.mul(mp.lmul(a, T0), Q)
    local _, s1 = mp.dwadd(a, ZERO, mq0, mq1)
    return reduce(s1)
end

--- Encodes a scalar.
--
-- @tparam {number...} a A number 2²⁶⁴ × a mod q as 11 limbs in [0..2²⁴).
-- @treturn string The 32-byte string encoding of a.
--
local function encode(a)
    return pfq(fmtfq, unpack(demontgomery(a)))
end

--- Decodes a scalar.
--
-- @tparam string str A 32-byte string encoding some little-endian number a.
-- @treturn {number...} 2²⁶⁴ × a mod q as 11 limbs in [0..2²⁴).
--
local function decode(str)
    local dec = {ufq(fmtfq, str, 1)} dec[12] = nil
    return montgomery(dec)
end

--- Decodes a scalar from a "wide" string.
--
-- @tparam string str A 64-byte string encoding some little-endian number a.
-- @treturn {number...} 2²⁶⁴ × a mod q as 11 limbs in [0..2²⁴).
--
local function decodeWide(str)
    local low = {ufql(fmtfql, str, 1)} low[12] = nil
    local high = {ufqh(fmtfqh, str, 34)} high[12] = nil
    return add(montgomery(low), montgomery(montgomery(high)))
end

--- Decodes a scalar using the X25519/Ed25519 bit clamping scheme.
--
-- @tparam string str A 32-byte string encoding some little-endian number a.
-- @treturn {number...} 2²⁶⁴ × clamp(a) mod q as 11 limbs in [0..2²⁴).
--
local function decodeClamped(str)
    -- Decode.
    local words = {ufq(fmtfq, str, 1)} words[12] = nil

    -- Clamp.
    words[1] = bit32.band(words[1], 0xfffff8)
    words[11] = bit32.band(words[11], 0x7fff)
    words[11] = bit32.bor(words[11], 0x4000)

    return montgomery(words)
end

--- Divides a scalar by 8.
--
-- @tparam {number...} 2²⁶⁴ × a mod q as 11 limbs in [0..2²⁴).
-- @treturn {number...} 2²⁶⁵ × a ÷ 8 mod q as 11 limbs in [0..2²⁴).
local function eighth(a)
    return mul(a, T8)
end

--- Encodes a scalar for round-trip with dedcodeClamped.
--
-- Only values that are in the decodeClamped range can be re-encoded like this.
--
-- @tparam {number...} a A number 2²⁶⁴ × a mod q as 11 limbs in [0..2²⁴).
-- @treturn string The 32-byte string encoding of 8(a ÷ 8 mod q).
--
local function encodeClamped(a)
    local c1 = demontgomery(eighth(a))
    local c2 = mp.lmul(c1, mp.num(8))
    return pfq(fmtfq, unpack(c2))
end

--- Returns a scalar in binary.
--
-- @tparam {number...} a A number a < q as 11 limbs in [0..2²⁴).
-- @treturn {number...} 2⁻²⁶⁴ × a mod q as 253 bits.
--
local function bits(a)
    local out = util.rebaseLE(demontgomery(a), 2 ^ 24, 2)
    for i = 254, 289 do out[i] = nil end
    return out
end

--- Makes a PRAC ruleset from a pair of scalars.
--
-- For more information see section 3.3 of Speeding up subgroup cryptosystems:
-- Martijn Stam. Speeding up subgroup cryptosystems. PhD thesis, Technische
-- Universiteit Eindhoven, 2003. https://dx.doi.org/10.6100/IR564670.
--
-- @tparam {number...} a A scalar 2²⁶⁴ × a mod q as 11 limbs in [0..2²⁴).
-- @tparam {number...} b A scalar 2²⁶⁴ × b mod q as 11 limbs in [0..2²⁴).
-- @treturn {{number...}, {number...}} The generated ruleset.
--
local function makeRuleset(a, b)
    -- The numbers in raw multiprecision tables.
    ---@type MpSW11L24
    local dt = demontgomery(a) -- (-2²⁴..2²⁴)
    ---@type MpSW11L24
    local et = demontgomery(b) -- (-2²⁴..2²⁴)
    ---@type MpSW11L24
    local ft = mp.carryWeak(mp.sub(dt, et))  -- (-2²⁴..2²⁴)

    -- Residue classes of (d, e) modulo 2.
    local d2 = mp.mod2(dt)
    local e2 = mp.mod2(et)

    -- Residue classes of (d, e) modulo 3.
    local d3 = mp.mod3(dt)
    local e3 = mp.mod3(et)

    -- (e, d - e) in limited-precision floating-point numbers.
    local ef = mp.approx(et)
    local ff = mp.approx(ft)

    -- Lookup table for inversions and halvings modulo 3.
    local lut3 = {[0] = 0, 2, 1}

    local rules = {}
    while true do
        local cmp = mp.cmp(mp.carry(dt), mp.carry(et))
        if cmp == 0 then
            break
        elseif cmp < 0 then
            -- M0. d < e
            rules[#rules + 1] = 0
            -- (d, e) ← (e, d)
            dt, et = et, dt
            d2, e2 = e2, d2
            d3, e3 = e3, d3
            ef = mp.approx(et)
            ft = mp.carry(mp.sub(dt, et))
            ff = -ff
        elseif 4 * ff - ef < -1 and d3 == lut3[e3] then
            -- M1. e < d ≤ 5/4 e, d ≡ -e (mod 3)
            rules[#rules + 1] = 1
            -- (d, e) ← ((2d - e)/3, (2e - d)/3)
            dt = mp.third(mp.carryWeak(mp.add(dt, ft)))
            et = mp.third(mp.carryWeak(mp.sub(et, ft)))
            d2, e2 = e2, d2
            d3, e3 = mp.mod3(dt), mp.mod3(et)
            ef = mp.approx(et)
        elseif 4 * ff - ef < -1 and d2 == e2 and d3 == e3 then
            -- M2. e < d ≤ 5/4 e, d ≡ e (mod 6)
            rules[#rules + 1] = 2
            -- (d, e) ← ((d - e)/2, e)
            dt = mp.half(ft)
            d2 = mp.mod2(dt)
            d3 = lut3[(d3 - e3) % 3]
            ft = mp.carryWeak(mp.sub(dt, et))
            ff = mp.approx(ft)
        elseif ff - 3 * ef < -1 then
            -- M3. d ≤ 4e
            rules[#rules + 1] = 3
            -- (d, e) ← (d - e, e)
            dt = mp.carryWeak(ft)
            d2 = (d2 - e2) % 2
            d3 = (d3 - e3) % 3
            ft = mp.carryWeak(mp.sub(dt, et))
            ff = mp.approx(ft)
        elseif d2 == e2 then
            -- M4. d ≡ e (mod 2)
            rules[#rules + 1] = 2
            -- (d, e) ← ((d - e)/2, e)
            dt = mp.half(ft)
            d2 = mp.mod2(dt)
            d3 = lut3[(d3 - e3) % 3]
            ft = mp.carryWeak(mp.sub(dt, et))
            ff = mp.approx(ft)
        elseif d2 == 0 then
            -- M5. d ≡ 0 (mod 2)
            rules[#rules + 1] = 5
            -- (d, e) ← (d/2, e)
            dt = mp.half(dt)
            d2 = mp.mod2(dt)
            d3 = lut3[d3]
            ft = mp.carryWeak(mp.sub(dt, et))
            ff = mp.approx(ft)
        elseif d3 == 0 then
            -- M6. d ≡ 0 (mod 3)
            rules[#rules + 1] = 6
            -- (d, e) ← (d/3 - e, e)
            dt = mp.carryWeak(mp.sub(mp.third(dt), et))
            d2 = (d2 - e2) % 2
            d3 = mp.mod3(dt)
            ft = mp.carryWeak(mp.sub(dt, et))
            ff = mp.approx(ft)
        elseif d3 == lut3[e3] then
            -- M7. d ≡ -e (mod 3)
            rules[#rules + 1] = 7
            -- (d, e) ← ((d - 2e)/3, e)
            dt = mp.third(mp.carryWeak(mp.sub(ft, et)))
            d3 = mp.mod3(dt)
            ft = mp.carryWeak(mp.sub(dt, et))
            ff = mp.approx(ft)
        elseif d3 == e3 then
            -- M8. d ≡ e (mod 3)
            rules[#rules + 1] = 8
            -- (d, e) ← ((d - e)/3, e)
            dt = mp.third(ft)
            d2 = (d2 - e2) % 2
            d3 = mp.mod3(dt)
            ft = mp.carryWeak(mp.sub(dt, et))
            ff = mp.approx(ft)
        else
            -- M9. e ≡ 0 (mod 2)
            rules[#rules + 1] = 9
            -- (d, e) ← (d, e/2)
            et = mp.half(et)
            e2 = mp.mod2(et)
            e3 = lut3[e3]
            ef = mp.approx(et)
            ft = mp.carryWeak(mp.sub(dt, et))
            ff = mp.approx(ft)
        end
    end

    local ubits = util.rebaseLE(dt, 2 ^ 24, 2)
    while ubits[#ubits] == 0 do ubits[#ubits] = nil end

    return {ubits, rules}
end

return {
    add = add,
    sub = sub,
    mul = mul,
    encode = encode,
    encodeClamped = encodeClamped,
    decode = decode,
    decodeWide = decodeWide,
    decodeClamped = decodeClamped,
    eighth = eighth,
    bits = bits,
    makeRuleset = makeRuleset,
}
]=],
["ccryptolib.internal.hw"]=[=[local config = require "ccryptolib.config"

local PERIPHERAL_CHECK_INTERVAL = 10

local peripheralName = nil
local peripheralType = nil
local peripheralSeed = nil
local lastCheck = -PERIPHERAL_CHECK_INTERVAL

local function isClassicPeripherals(name)
    local methods = peripheral.getMethods(name)
    if type(methods) ~= "table" then return false end
    local kMethods = {}
    for _, method in ipairs(methods) do
        kMethods[method] = true
    end
    if not kMethods.computeSharedSecret then return false end
    if not kMethods.deriveEcdhPublicKey then return false end
    if not kMethods.derivePublicKey then return false end
    if not kMethods.randomBytes then return false end
    if not kMethods.sha256 then return false end
    if not kMethods.sha512 then return false end
    if not kMethods.sign then return false end
    if not kMethods.verify then return false end
    return true
end

local function findPeripheral()
    if peripheralName ~= nil then return end
    if os.clock() - lastCheck < PERIPHERAL_CHECK_INTERVAL then return end
    lastCheck = os.clock()

    if not peripheral then
        config.usePeripherals(false)
        return
    end

    local found = peripheral.find("cryptographic_accelerator", isClassicPeripherals)
    if found then
        peripheralName = peripheral.getName(found)
        peripheralType = "cryptographic_accelerator"
        local ok, seed = pcall(peripheral.call, peripheralName, "randomBytes", 32)
        if ok then peripheralSeed = seed end
    end
end

local function takeSeed()
    if not config.usePeripherals() then return end
    findPeripheral()
    local out = peripheralSeed
    peripheralSeed = nil
    return out
end

local function checkedCall(method, ...)
    if not config.usePeripherals() then return false end
    findPeripheral()
    if not peripheralName then return false end
    if peripheral.hasType(peripheralName, peripheralType) then
        local ok, out = pcall(peripheral.call, peripheralName, method, ...)
        if not ok or out == nil then return false end
        return true, out
    else
        peripheralName = nil
        peripheralType = nil
        return false
    end
end

local function x25519Exchange(sk, pk)
    return checkedCall("computeSharedSecret", sk, pk)
end

local function x25519PublicKey(sk)
    return checkedCall("deriveEcdhPublicKey", sk)
end

local function ed25519PublicKey(sk)
    return checkedCall("derivePublicKey", sk)
end

local function random(length)
    return checkedCall("randomBytes", length)
end

local function sha256(input)
    return checkedCall("sha256", input, false)
end

local function sha512(input)
    return checkedCall("sha512", input, false)
end

local function ed25519Sign(m, sk)
    return checkedCall("sign", m, sk)
end

local function ed25519Verify(m, s, pk)
    return checkedCall("verify", m, s, pk)
end

return {
    takeSeed = takeSeed,
    x25519Exchange = x25519Exchange,
    x25519PublicKey = x25519PublicKey,
    ed25519PublicKey = ed25519PublicKey,
    random = random,
    sha256 = sha256,
    sha512 = sha512,
    ed25519Sign = ed25519Sign,
    ed25519Verify = ed25519Verify,
}
]=],
["ccryptolib.internal.mp"]=[=[--- Multi-precision arithmetic on 264-bit integers.

local unpack = unpack or table.unpack

--- A little-endian big integer of width 11 in (-2⁵²..2⁵²).
--- @class MpSW11L52

--- A little-endian big integer of width 11 in (-2²⁴, 2²⁴).
--- @class MpSW11L24: MpSW11L52

--- A little-endian big integer of width 11 in [0..2²⁴).
--- @class MpUW11L24: MpSW11L24

--- Carries a number in base 2²⁴ into a signed limb form.
--- @param a MpSW11L52
--- @return MpSW11L24 low The carried low limbs.
--- @return number carry The overflowed carry.
local function carryWeak(a)
    local a00, a01, a02, a03, a04, a05, a06, a07, a08, a09, a10 = unpack(a)

    local h00 = a00 + 3 * 2 ^ 75 - 3 * 2 ^ 75 a01 = a01 + h00 * 2 ^ -24
    local h01 = a01 + 3 * 2 ^ 75 - 3 * 2 ^ 75 a02 = a02 + h01 * 2 ^ -24
    local h02 = a02 + 3 * 2 ^ 75 - 3 * 2 ^ 75 a03 = a03 + h02 * 2 ^ -24
    local h03 = a03 + 3 * 2 ^ 75 - 3 * 2 ^ 75 a04 = a04 + h03 * 2 ^ -24
    local h04 = a04 + 3 * 2 ^ 75 - 3 * 2 ^ 75 a05 = a05 + h04 * 2 ^ -24
    local h05 = a05 + 3 * 2 ^ 75 - 3 * 2 ^ 75 a06 = a06 + h05 * 2 ^ -24
    local h06 = a06 + 3 * 2 ^ 75 - 3 * 2 ^ 75 a07 = a07 + h06 * 2 ^ -24
    local h07 = a07 + 3 * 2 ^ 75 - 3 * 2 ^ 75 a08 = a08 + h07 * 2 ^ -24
    local h08 = a08 + 3 * 2 ^ 75 - 3 * 2 ^ 75 a09 = a09 + h08 * 2 ^ -24
    local h09 = a09 + 3 * 2 ^ 75 - 3 * 2 ^ 75 a10 = a10 + h09 * 2 ^ -24
    local h10 = a10 + 3 * 2 ^ 75 - 3 * 2 ^ 75

    return {
        a00 - h00,
        a01 - h01,
        a02 - h02,
        a03 - h03,
        a04 - h04,
        a05 - h05,
        a06 - h06,
        a07 - h07,
        a08 - h08,
        a09 - h09,
        a10 - h10,
    }, h10 * 2 ^ -24
end

--- Carries a number in base 2²⁴.
--- @param a MpSW11L52
--- @return MpUW11L24 low The low 11 limbs of the output.
--- @return number carry The overflow carry.
local function carry(a)
    local a00, a01, a02, a03, a04, a05, a06, a07, a08, a09, a10 = unpack(a)

    local l00 = a00 % 2 ^ 24 a01 = a01 + (a00 - l00) * 2 ^ -24
    local l01 = a01 % 2 ^ 24 a02 = a02 + (a01 - l01) * 2 ^ -24
    local l02 = a02 % 2 ^ 24 a03 = a03 + (a02 - l02) * 2 ^ -24
    local l03 = a03 % 2 ^ 24 a04 = a04 + (a03 - l03) * 2 ^ -24
    local l04 = a04 % 2 ^ 24 a05 = a05 + (a04 - l04) * 2 ^ -24
    local l05 = a05 % 2 ^ 24 a06 = a06 + (a05 - l05) * 2 ^ -24
    local l06 = a06 % 2 ^ 24 a07 = a07 + (a06 - l06) * 2 ^ -24
    local l07 = a07 % 2 ^ 24 a08 = a08 + (a07 - l07) * 2 ^ -24
    local l08 = a08 % 2 ^ 24 a09 = a09 + (a08 - l08) * 2 ^ -24
    local l09 = a09 % 2 ^ 24 a10 = a10 + (a09 - l09) * 2 ^ -24
    local l10 = a10 % 2 ^ 24
    local h10 = (a10 - l10) * 2 ^ -24

    return {l00, l01, l02, l03, l04, l05, l06, l07, l08, l09, l10}, h10
end

--- Adds two numbers.
--- @param a MpSW11L24
--- @param b MpSW11L24
--- @return MpSW11L52 c a + b
local function add(a, b)
    local a00, a01, a02, a03, a04, a05, a06, a07, a08, a09, a10 = unpack(a)
    local b00, b01, b02, b03, b04, b05, b06, b07, b08, b09, b10 = unpack(b)

    return {
        a00 + b00,
        a01 + b01,
        a02 + b02,
        a03 + b03,
        a04 + b04,
        a05 + b05,
        a06 + b06,
        a07 + b07,
        a08 + b08,
        a09 + b09,
        a10 + b10,
    }
end

--- Subtracts a number from another.
--- @param a MpSW11L24
--- @param b MpSW11L24
--- @return MpSW11L52 c a - b
local function sub(a, b)
    local a00, a01, a02, a03, a04, a05, a06, a07, a08, a09, a10 = unpack(a)
    local b00, b01, b02, b03, b04, b05, b06, b07, b08, b09, b10 = unpack(b)

    return {
        a00 - b00,
        a01 - b01,
        a02 - b02,
        a03 - b03,
        a04 - b04,
        a05 - b05,
        a06 - b06,
        a07 - b07,
        a08 - b08,
        a09 - b09,
        a10 - b10,
    }
end

--- Computes the lower half of a product between two numbers.
--- @param a MpUW11L24
--- @param b MpUW11L24
--- @return MpUW11L24 c a × b (mod 2²⁶⁴)
--- @return number carry ⌊a × b ÷ 2²⁶⁴⌋
local function lmul(a, b)
    local a00, a01, a02, a03, a04, a05, a06, a07, a08, a09, a10 = unpack(a)
    local b00, b01, b02, b03, b04, b05, b06, b07, b08, b09, b10 = unpack(b)

    return carry {
        a00 * b00,
        a01 * b00 + a00 * b01,
        a02 * b00 + a01 * b01 + a00 * b02,
        a03 * b00 + a02 * b01 + a01 * b02 + a00 * b03,
        a04 * b00 + a03 * b01 + a02 * b02 + a01 * b03 + a00 * b04,
        a05 * b00 + a04 * b01 + a03 * b02 + a02 * b03 + a01 * b04 + a00 * b05,
        a06 * b00 + a05 * b01 + a04 * b02 + a03 * b03 + a02 * b04 + a01 * b05 + a00 * b06,
        a07 * b00 + a06 * b01 + a05 * b02 + a04 * b03 + a03 * b04 + a02 * b05 + a01 * b06 + a00 * b07,
        a08 * b00 + a07 * b01 + a06 * b02 + a05 * b03 + a04 * b04 + a03 * b05 + a02 * b06 + a01 * b07 + a00 * b08,
        a09 * b00 + a08 * b01 + a07 * b02 + a06 * b03 + a05 * b04 + a04 * b05 + a03 * b06 + a02 * b07 + a01 * b08 + a00 * b09,
        a10 * b00 + a09 * b01 + a08 * b02 + a07 * b03 + a06 * b04 + a05 * b05 + a04 * b06 + a03 * b07 + a02 * b08 + a01 * b09 + a00 * b10,
    }
end

--- Computes the a product between two numbers.
--- @param a MpUW11L24
--- @param b MpUW11L24
--- @return MpUW11L24 low The low 11 limbs of a × b.
--- @return MpUW11L24 high The high 11 limbs of a × b.
local function mul(a, b)
    local low, of = lmul(a, b)

    local _, a01, a02, a03, a04, a05, a06, a07, a08, a09, a10 = unpack(a)
    local _, b01, b02, b03, b04, b05, b06, b07, b08, b09, b10 = unpack(b)

    -- The carry is always 0.
    return low, (carry {
        of + a10 * b01 + a09 * b02 + a08 * b03 + a07 * b04 + a06 * b05 + a05 * b06 + a04 * b07 + a03 * b08 + a02 * b09 + a01 * b10,
        a10 * b02 + a09 * b03 + a08 * b04 + a07 * b05 + a06 * b06 + a05 * b07 + a04 * b08 + a03 * b09 + a02 * b10,
        a10 * b03 + a09 * b04 + a08 * b05 + a07 * b06 + a06 * b07 + a05 * b08 + a04 * b09 + a03 * b10,
        a10 * b04 + a09 * b05 + a08 * b06 + a07 * b07 + a06 * b08 + a05 * b09 + a04 * b10,
        a10 * b05 + a09 * b06 + a08 * b07 + a07 * b08 + a06 * b09 + a05 * b10,
        a10 * b06 + a09 * b07 + a08 * b08 + a07 * b09 + a06 * b10,
        a10 * b07 + a09 * b08 + a08 * b09 + a07 * b10,
        a10 * b08 + a09 * b09 + a08 * b10,
        a10 * b09 + a09 * b10,
        a10 * b10,
        0
    })
end

--- Computes a double-width sum of two numbers.
--- @param a0 MpUW11L24 The low 11 limbs of a.
--- @param a1 MpUW11L24 The high 11 limbs of a.
--- @param b0 MpUW11L24 The low 11 limbs of b.
--- @param b1 MpUW11L24 The high 11 limbs of b.
--- @return MpUW11L24 c0 The low 11 limbs of a + b.
--- @return MpUW11L24 c1 The high 11 limbs of a + b.
--- @return number The carry.
local function dwadd(a0, a1, b0, b1)
    local low, c = carry(add(a0, b0))
    local high = add(a1, b1)
    high[1] = high[1] + c
    return low, carry(high)
end

--- Computes half of a number.
--- @param a MpSW11L24 The number to halve, must be even.
--- @return MpSW11L24 c a ÷ 2
local function half(a)
    local a00, a01, a02, a03, a04, a05, a06, a07, a08, a09, a10 = unpack(a)

    return (carryWeak {
        a00 * 0.5 + a01 * 2 ^ 23,
        a02 * 2 ^ 23,
        a03 * 2 ^ 23,
        a04 * 2 ^ 23,
        a05 * 2 ^ 23,
        a06 * 2 ^ 23,
        a07 * 2 ^ 23,
        a08 * 2 ^ 23,
        a09 * 2 ^ 23,
        a10 * 2 ^ 23,
        0,
    })
end

--- Computes a third of a number.
--- @param a MpSW11L24 The number to divide, must be a multiple of 3.
--- @return MpSW11L24 c a ÷ 3
local function third(a)
    local a00, a01, a02, a03, a04, a05, a06, a07, a08, a09, a10 = unpack(a)

    local d00 = a00 * 0xaaaaaa
    local d01 = a01 * 0xaaaaaa + d00
    local d02 = a02 * 0xaaaaaa + d01
    local d03 = a03 * 0xaaaaaa + d02
    local d04 = a04 * 0xaaaaaa + d03
    local d05 = a05 * 0xaaaaaa + d04
    local d06 = a06 * 0xaaaaaa + d05
    local d07 = a07 * 0xaaaaaa + d06
    local d08 = a08 * 0xaaaaaa + d07
    local d09 = a09 * 0xaaaaaa + d08
    local d10 = a10 * 0xaaaaaa + d09

    -- We compute the modular division mod 2²⁶⁴. The carry isn't 0 but it isn't
    -- part of a ÷ 3 either.
    return (carryWeak {
        a00 + d00,
        a01 + d01,
        a02 + d02,
        a03 + d03,
        a04 + d04,
        a05 + d05,
        a06 + d06,
        a07 + d07,
        a08 + d08,
        a09 + d09,
        a10 + d10,
    })
end

--- Computes a number modulo 2.
--- @param a MpSW11L24
--- @return number c a mod 2.
local function mod2(a)
    return a[1] % 2
end

--- Computes a number modulo 3.
--- @param a MpSW11L24
--- @return number c a mod 3.
local function mod3(a)
    local a00, a01, a02, a03, a04, a05, a06, a07, a08, a09, a10 = unpack(a)
    return (a00 + a01 + a02 + a03 + a04 + a05 + a06 + a07 + a08 + a09 + a10) % 3
end

--- Computes a double representing the most-significant bits of a number.
--- @param a MpSW11L52
--- @return number c A floating-point approximation for the value of a.
local function approx(a)
    local a00, a01, a02, a03, a04, a05, a06, a07, a08, a09, a10 = unpack(a)
    return a00
        + a01 * 2 ^ 24
        + a02 * 2 ^ 48
        + a03 * 2 ^ 72
        + a04 * 2 ^ 96
        + a05 * 2 ^ 120
        + a06 * 2 ^ 144
        + a07 * 2 ^ 168
        + a08 * 2 ^ 192
        + a09 * 2 ^ 216
        + a10 * 2 ^ 240
end

--- @param a MpUW11L24
--- @param b MpUW11L24
--- @return number c A number that compares to 0 the same as a compares to b.
local function cmp(a, b)
    return a[11] < b[11] and -1
        or a[11] > b[11] and 1
        or a[10] < b[10] and -1
        or a[10] > b[10] and 1
        or a[9] < b[9] and -1
        or a[9] > b[9] and 1
        or a[8] < b[8] and -1
        or a[8] > b[8] and 1
        or a[7] < b[7] and -1
        or a[7] > b[7] and 1
        or a[6] < b[6] and -1
        or a[6] > b[6] and 1
        or a[5] < b[5] and -1
        or a[5] > b[5] and 1
        or a[4] < b[4] and -1
        or a[4] > b[4] and 1
        or a[3] < b[3] and -1
        or a[3] > b[3] and 1
        or a[2] < b[2] and -1
        or a[2] > b[2] and 1
        or a[1] < b[1] and -1
        or a[1] > b[1] and 1
        or 0
end

local function num(a)
    return {a, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
end

return {
    carry = carry,
    carryWeak = carryWeak,
    add = add,
    sub = sub,
    dwadd = dwadd,
    lmul = lmul,
    mul = mul,
    half = half,
    third = third,
    mod2 = mod2,
    mod3 = mod3,
    approx = approx,
    cmp = cmp,
    num = num,
}
]=],
["ccryptolib.internal.packing"]=[=[--- High-performance binary packing of integers.
---
--- Remark (and warning):
--- For performance reasons, **the generated functions do not check types,
--- lengths, nor ranges**. You must ensure that the passed arguments are
--- well-formed and respect the format string yourself.

local fmt = string.format

local function mkPack(words, BE)
    local out = "local C=string.char return function(_,"
    local nb = 0
    for i = 1, #words do
        out = out .. fmt("n%d,", i)
        nb = nb + words[i]
    end
    out = out:sub(1, -2) .. ")local "
    for i = 1, nb do
        out = out .. fmt("b%d,", i)
    end
    out = out:sub(1, -2) .. " "
    local bi = 1
    for i = 1, #words do
        for _ = 1, words[i] - 1 do
            out = out .. fmt("b%d=n%d%%2^8 n%d=(n%d-b%d)*2^-8 ", bi, i, i, i, bi)
            bi = bi + 1
        end
        bi = bi + 1
    end
    out = out .. "return C("
    bi = 1
    if not BE then
        for i = 1, #words do
            for _ = 1, words[i] - 1 do
                out = out .. fmt("b%d,", bi)
                bi = bi + 1
            end
            out = out .. fmt("n%d%%2^8,", i)
            bi = bi + 1
        end
    else
        for i = 1, #words do
            out = out .. fmt("n%d%%2^8,", i)
            bi = bi + words[i] - 2
            for _ = 1, words[i] - 1 do
                out = out .. fmt("b%d,", bi)
                bi = bi - 1
            end
            bi = bi + words[i] + 1
        end
    end
    out = out:sub(1, -2) .. ")end"
    return load(out)()
end

local function mkUnpack(words, BE)
    local out = "local B=string.byte return function(_,s,i)local "
    local bi = 1
    if not BE then
        for i = 1, #words do
            for _ = 1, words[i] do
                out = out .. fmt("b%d,", bi)
                bi = bi + 1
            end
        end
    else
        for i = 1, #words do
            bi = bi + words[i] - 1
            for _ = 1, words[i] do
                out = out .. fmt("b%d,", bi)
                bi = bi - 1
            end
            bi = bi + words[i] + 1
        end
    end
    out = out:sub(1, -2) .. fmt("=B(s,i,i+%d)return ", bi - 2)
    bi = 1
    for i = 1, #words do
        out = out .. fmt("b%d", bi)
        bi = bi + 1
        for j = 2, words[i] do
            out = out .. fmt("+b%d*2^%d", bi, 8 * j - 8)
            bi = bi + 1
        end
        out = out .. ","
    end
    out = out .. fmt("i+%d end", bi - 1)
    return load(out)()
end

-- Check whether string.pack is implemented in a high-speed language.
if not string.pack or pcall(string.dump, string.pack) then
    local function compile(fmt, fn)
        local e = assert(fmt:match("^([><])I[I%d]+$"), "invalid format string")
        local w = {}
        for i in fmt:gmatch("I([%d]+)") do
            local n = tonumber(i) or 4
            assert(n > 0 and n <= 16, "integral size out of limits")
            w[#w + 1] = n
        end
        return fn(w, e == ">")
    end

    local packCache = {}
    local unpackCache = {}

    -- I CAN'T EVEN WITH THIS EXTENSION, WHY CAN'T IT HANDLE MORE THAN A SINGLE
    -- LINE OF RETURN DESCRIPTION? LOOK AT IT!!! THE COMMENT GOES OVER THERE ------------------------------------------------------------------> look! ↓ ↓ ↓

    --- (string.pack is nil) Compiles a binary packing function.
    ---
    --- Errors if the format string is invalid or has an invalid integral size,
    --- or if the compiled function turns out too large.
    ---
    --- @param fmt string A string matched by `^([><])I[I%d]+$`.
    --- @return fun(_ignored: any, ...: any): string pack A function that behaves like an unsafe version of `string.pack` for the given format string.
    --- @return string fmt
    local function compilePack(fmt)
        if not packCache[fmt] then
            packCache[fmt] = compile(fmt, mkPack)
        end
        return packCache[fmt], fmt
    end

    --- (string.pack is nil) Compiles a binary unpacking function.
    ---
    --- Errors if the format string is invalid or has an invalid integral size,
    --- or if the compiled function turns out too large.
    ---
    --- @param fmt string A string matched by `^([><])I[I%d]+$`.
    --- @return fun(_ignored: any, str: string, pos: number) unpack A function that behaves like an unsafe version of `string.unpack` for the given format string. Note that the third argument isn't optional.
    --- @return string fmt
    local function compileUnpack(fmt)
        if not unpackCache[fmt] then
            unpackCache[fmt] = compile(fmt, mkUnpack)
        end
        return unpackCache[fmt], fmt
    end

    return {
        compilePack = compilePack,
        compileUnpack = compileUnpack,
    }
else
    --- (string.pack isn't nil) It's string.pack! It returns string.pack!
    --- @param fmt string
    --- @return fun(fmt: string, ...: any): string pack string.pack!
    --- @return string fmt
    local function compilePack(fmt) return string.pack, fmt end

    --- (string.pack isn't nil) It's string.unpack! It returns string.unpack!
    --- @param fmt string
    --- @return fun(fmt: string, str: string, pos: number) unpack string.unpack!
    --- @return string fmt
    local function compileUnpack(fmt) return string.unpack, fmt end

    return {
        compilePack = compilePack,
        compileUnpack = compileUnpack,
    }
end
]=],
["ccryptolib.internal.sha512"]=[=[--- The SHA512 cryptographic hash function.

local expect = require "cc.expect".expect
local packing = require "ccryptolib.internal.packing"
local hw = require "ccryptolib.internal.hw"

local shl = bit32.lshift
local shr = bit32.rshift
local bxor = bit32.bxor
local bnot = bit32.bnot
local band = bit32.band
local p1x16, fmt1x16 = packing.compilePack(">I16")
local p16x4, fmt16x4 = packing.compilePack(">I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4")
local u32x4, fmt32x4 = packing.compileUnpack(">I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4")

local function carry64(a1, a0)
    local r0 = a0 % 2 ^ 32
    a1 = a1 + (a0 - r0) / 2 ^ 32
    return a1 % 2 ^ 32, r0
end

local K = {
    0x428a2f98, 0xd728ae22, 0x71374491, 0x23ef65cd, 0xb5c0fbcf, 0xec4d3b2f,
    0xe9b5dba5, 0x8189dbbc, 0x3956c25b, 0xf348b538, 0x59f111f1, 0xb605d019,
    0x923f82a4, 0xaf194f9b, 0xab1c5ed5, 0xda6d8118, 0xd807aa98, 0xa3030242,
    0x12835b01, 0x45706fbe, 0x243185be, 0x4ee4b28c, 0x550c7dc3, 0xd5ffb4e2,
    0x72be5d74, 0xf27b896f, 0x80deb1fe, 0x3b1696b1, 0x9bdc06a7, 0x25c71235,
    0xc19bf174, 0xcf692694, 0xe49b69c1, 0x9ef14ad2, 0xefbe4786, 0x384f25e3,
    0x0fc19dc6, 0x8b8cd5b5, 0x240ca1cc, 0x77ac9c65, 0x2de92c6f, 0x592b0275,
    0x4a7484aa, 0x6ea6e483, 0x5cb0a9dc, 0xbd41fbd4, 0x76f988da, 0x831153b5,
    0x983e5152, 0xee66dfab, 0xa831c66d, 0x2db43210, 0xb00327c8, 0x98fb213f,
    0xbf597fc7, 0xbeef0ee4, 0xc6e00bf3, 0x3da88fc2, 0xd5a79147, 0x930aa725,
    0x06ca6351, 0xe003826f, 0x14292967, 0x0a0e6e70, 0x27b70a85, 0x46d22ffc,
    0x2e1b2138, 0x5c26c926, 0x4d2c6dfc, 0x5ac42aed, 0x53380d13, 0x9d95b3df,
    0x650a7354, 0x8baf63de, 0x766a0abb, 0x3c77b2a8, 0x81c2c92e, 0x47edaee6,
    0x92722c85, 0x1482353b, 0xa2bfe8a1, 0x4cf10364, 0xa81a664b, 0xbc423001,
    0xc24b8b70, 0xd0f89791, 0xc76c51a3, 0x0654be30, 0xd192e819, 0xd6ef5218,
    0xd6990624, 0x5565a910, 0xf40e3585, 0x5771202a, 0x106aa070, 0x32bbd1b8,
    0x19a4c116, 0xb8d2d0c8, 0x1e376c08, 0x5141ab53, 0x2748774c, 0xdf8eeb99,
    0x34b0bcb5, 0xe19b48a8, 0x391c0cb3, 0xc5c95a63, 0x4ed8aa4a, 0xe3418acb,
    0x5b9cca4f, 0x7763e373, 0x682e6ff3, 0xd6b2b8a3, 0x748f82ee, 0x5defb2fc,
    0x78a5636f, 0x43172f60, 0x84c87814, 0xa1f0ab72, 0x8cc70208, 0x1a6439ec,
    0x90befffa, 0x23631e28, 0xa4506ceb, 0xde82bde9, 0xbef9a3f7, 0xb2c67915,
    0xc67178f2, 0xe372532b, 0xca273ece, 0xea26619c, 0xd186b8c7, 0x21c0c207,
    0xeada7dd6, 0xcde0eb1e, 0xf57d4f7f, 0xee6ed178, 0x06f067aa, 0x72176fba,
    0x0a637dc5, 0xa2c898a6, 0x113f9804, 0xbef90dae, 0x1b710b35, 0x131c471b,
    0x28db77f5, 0x23047d84, 0x32caab7b, 0x40c72493, 0x3c9ebe0a, 0x15c9bebc,
    0x431d67c4, 0x9c100d4c, 0x4cc5d4be, 0xcb3e42b6, 0x597f299c, 0xfc657e2a,
    0x5fcb6fab, 0x3ad6faec, 0x6c44198c, 0x4a475817,
}

--- Hashes data bytes using SHA512.
--- @param data string The input data.
--- @return string hash The 64-byte hash value.
local function digest(data)
    expect(1, data, "string")
    local ok, out = hw.sha512(data)
    if ok then return out end

    -- Pad input.
    local bitlen = #data * 8
    local padlen = -(#data + 17) % 128
    data = data .. "\x80" .. ("\0"):rep(padlen) .. p1x16(fmt1x16, bitlen)

    -- Initialize state.
    local h01, h00 = 0x6a09e667, 0xf3bcc908
    local h11, h10 = 0xbb67ae85, 0x84caa73b
    local h21, h20 = 0x3c6ef372, 0xfe94f82b
    local h31, h30 = 0xa54ff53a, 0x5f1d36f1
    local h41, h40 = 0x510e527f, 0xade682d1
    local h51, h50 = 0x9b05688c, 0x2b3e6c1f
    local h61, h60 = 0x1f83d9ab, 0xfb41bd6b
    local h71, h70 = 0x5be0cd19, 0x137e2179

    -- Digest.
    for i = 1, #data, 128 do
        local w = {u32x4(fmt32x4, data, i)}

        -- Message schedule.
        for j = 33, 160, 2 do
            local wf1, wf0 = w[j - 30], w[j - 29]
            local t1 = shr(wf1, 1) + shl(wf0, 31)
            local t0 = shr(wf0, 1) + shl(wf1, 31)
            local u1 = shr(wf1, 8) + shl(wf0, 24)
            local u0 = shr(wf0, 8) + shl(wf1, 24)
            local v1 = shr(wf1, 7)
            local v0 = shr(wf0, 7) + shl(wf1, 25)

            local w21, w20 = w[j - 4], w[j - 3]
            local w1 = shr(w21, 19) + shl(w20, 13)
            local w0 = shr(w20, 19) + shl(w21, 13)
            local x0 = shr(w21, 29) + shl(w20, 3)
            local x1 = shr(w20, 29) + shl(w21, 3)
            local y1 = shr(w21, 6)
            local y0 = shr(w20, 6) + shl(w21, 26)

            local r1, r0 =
                w[j - 32] + bxor(t1, u1, v1) + w[j - 14] + bxor(w1, x1, y1),
                w[j - 31] + bxor(t0, u0, v0) + w[j - 13] + bxor(w0, x0, y0)

            w[j], w[j + 1] = carry64(r1, r0)
        end

        -- Block function.
        local a1, a0 = h01, h00
        local b1, b0 = h11, h10
        local c1, c0 = h21, h20
        local d1, d0 = h31, h30
        local e1, e0 = h41, h40
        local f1, f0 = h51, h50
        local g1, g0 = h61, h60
        local h1, h0 = h71, h70
        for j = 1, 160, 2 do
            local t1 = shr(e1, 14) + shl(e0, 18)
            local t0 = shr(e0, 14) + shl(e1, 18)
            local u1 = shr(e1, 18) + shl(e0, 14)
            local u0 = shr(e0, 18) + shl(e1, 14)
            local v0 = shr(e1, 9) + shl(e0, 23)
            local v1 = shr(e0, 9) + shl(e1, 23)
            local s11 = bxor(t1, u1, v1)
            local s10 = bxor(t0, u0, v0)

            local ch1 = bxor(band(e1, f1), band(bnot(e1), g1))
            local ch0 = bxor(band(e0, f0), band(bnot(e0), g0))

            local temp11 = h1 + s11 + ch1 + K[j] + w[j]
            local temp10 = h0 + s10 + ch0 + K[j + 1] + w[j + 1]

            local w1 = shr(a1, 28) + shl(a0, 4)
            local w0 = shr(a0, 28) + shl(a1, 4)
            local x0 = shr(a1, 2) + shl(a0, 30)
            local x1 = shr(a0, 2) + shl(a1, 30)
            local y0 = shr(a1, 7) + shl(a0, 25)
            local y1 = shr(a0, 7) + shl(a1, 25)
            local s01 = bxor(w1, x1, y1)
            local s00 = bxor(w0, x0, y0)

            local maj1 = bxor(band(a1, b1), band(a1, c1), band(b1, c1))
            local maj0 = bxor(band(a0, b0), band(a0, c0), band(b0, c0))

            local temp21 = s01 + maj1
            local temp20 = s00 + maj0

            h1 = g1  h0 = g0
            g1 = f1  g0 = f0
            f1 = e1  f0 = e0
            e1, e0 = carry64(d1 + temp11, d0 + temp10)
            d1 = c1  d0 = c0
            c1 = b1  c0 = b0
            b1 = a1  b0 = a0
            a1, a0 = carry64(temp11 + temp21, temp10 + temp20)
        end

        h01, h00 = carry64(h01 + a1, h00 + a0)
        h11, h10 = carry64(h11 + b1, h10 + b0)
        h21, h20 = carry64(h21 + c1, h20 + c0)
        h31, h30 = carry64(h31 + d1, h30 + d0)
        h41, h40 = carry64(h41 + e1, h40 + e0)
        h51, h50 = carry64(h51 + f1, h50 + f0)
        h61, h60 = carry64(h61 + g1, h60 + g0)
        h71, h70 = carry64(h71 + h1, h70 + h0)
    end

    return p16x4(fmt16x4,
        h01, h00, h11, h10, h21, h20, h31, h30,
        h41, h40, h51, h50, h61, h60, h71, h70
    )
end

return {
    digest = digest,
}
]=],
["ccryptolib.internal.util"]=[=[local function lassert(val, err, level)
    if not val then error(err, level + 1) end
    return val
end

--- Converts a little-endian array from one power-of-two base to another.
--- @param a number[] The array to convert, in little-endian.
--- @param base1 number The base to convert from. Must be a power of 2.
--- @param base2 number The base to convert to. Must be a power of 2.
--- @return number[]
local function rebaseLE(a, base1, base2) -- TODO Write contract properly.
    local out = {}
    local outlen = 1
    local acc = 0
    local mul = 1
    for i = 1, #a do
        acc = acc + a[i] * mul
        mul = mul * base1
        while mul >= base2 do
            local rem = acc % base2
            acc = (acc - rem) / base2
            mul = mul / base2
            out[outlen] = rem
            outlen = outlen + 1
        end
    end
    if mul > 0 then
        out[outlen] = acc
    end
    return out
end

--- Decodes bits with X25519/Ed25519 exponent clamping.
--- @param str string The 32-byte encoded exponent.
--- @return number[] bits The decoded clamped bits.
local function bits(str)
    -- Decode.
    local bytes = {str:byte(1, 32)}
    local out = {}
    for i = 1, 32 do
        local byte = bytes[i]
        for j = -7, 0 do
            local bit = byte % 2
            out[8 * i + j] = bit
            byte = (byte - bit) / 2
        end
    end

    -- Clamp.
    out[1] = 0
    out[2] = 0
    out[3] = 0
    out[255] = 1
    out[256] = 0

    return out
end

--- Decodes bits with X25519/Ed25519 exponent clamping and division by 8.
--- @param str string The 32-byte encoded exponent.
--- @return number[] bits The decoded clamped bits, divided by 8.
local function bits8(str)
    return {unpack(bits(str), 4)}
end

return {
    lassert = lassert,
    rebaseLE = rebaseLE,
    bits = bits,
    bits8 = bits8,
}
]=],
["ccryptolib.poly1305"]=[=[--- The Poly1305 one-time authenticator.

local expect  = require "cc.expect".expect
local lassert = require "ccryptolib.internal.util".lassert
local packing = require "ccryptolib.internal.packing"

local u4x4, fmt4x4 = packing.compileUnpack("<I4I4I4I4")
local p4x4 = packing.compilePack(fmt4x4)

--- Computes a Poly1305 message authentication code.
--- @param key string A 32-byte single-use random key.
--- @param message string The message to authenticate.
--- @return string tag The 16-byte authentication tag.
local function mac(key, message)
    expect(1, key, "string")
    lassert(#key == 32, "key length must be 32", 2)
    expect(2, message, "string")

    -- Pad message.
    local pbplen = #message - 15
    if #message % 16 ~= 0 or #message == 0 then
        message = message .. "\1"
        message = message .. ("\0"):rep(-#message % 16)
    end

    -- Decode r.
    local R0, R1, R2, R3 = u4x4(fmt4x4, key, 1)

    -- Clamp and shift.
    R0 = R0 % 2 ^ 28
    R1 = (R1 - R1 % 4) % 2 ^ 28 * 2 ^ 32
    R2 = (R2 - R2 % 4) % 2 ^ 28 * 2 ^ 64
    R3 = (R3 - R3 % 4) % 2 ^ 28 * 2 ^ 96

    -- Split.
    local r0 = R0 % 2 ^ 18   local r1 = R0 - r0
    local r2 = R1 % 2 ^ 50   local r3 = R1 - r2
    local r4 = R2 % 2 ^ 82   local r5 = R2 - r4
    local r6 = R3 % 2 ^ 112  local r7 = R3 - r6

    -- Generate scaled key.
    local S1 = 5 / 2 ^ 130 * R1
    local S2 = 5 / 2 ^ 130 * R2
    local S3 = 5 / 2 ^ 130 * R3

    -- Split.
    local s2 = S1 % 2 ^ -80  local s3 = S1 - s2
    local s4 = S2 % 2 ^ -48  local s5 = S2 - s4
    local s6 = S3 % 2 ^ -16  local s7 = S3 - s6

    local h0, h1, h2, h3, h4, h5, h6, h7 = 0, 0, 0, 0, 0, 0, 0, 0

    for i = 1, #message, 16 do
        -- Decode message block.
        local m0, m1, m2, m3 = u4x4(fmt4x4, message, i)

        -- Shift message and add.
        local x0 = h0 + h1 + m0
        local x2 = h2 + h3 + m1 * 2 ^ 32
        local x4 = h4 + h5 + m2 * 2 ^ 64
        local x6 = h6 + h7 + m3 * 2 ^ 96

        -- Apply per-block padding when applicable.
        if i <= pbplen then x6 = x6 + 2 ^ 128 end

        -- Multiply
        h0 = x0 * r0 + x2 * s6 + x4 * s4 + x6 * s2
        h1 = x0 * r1 + x2 * s7 + x4 * s5 + x6 * s3
        h2 = x0 * r2 + x2 * r0 + x4 * s6 + x6 * s4
        h3 = x0 * r3 + x2 * r1 + x4 * s7 + x6 * s5
        h4 = x0 * r4 + x2 * r2 + x4 * r0 + x6 * s6
        h5 = x0 * r5 + x2 * r3 + x4 * r1 + x6 * s7
        h6 = x0 * r6 + x2 * r4 + x4 * r2 + x6 * r0
        h7 = x0 * r7 + x2 * r5 + x4 * r3 + x6 * r1

        -- Carry.
        local y0 = h0 + 3 * 2 ^ 69  - 3 * 2 ^ 69   h0 = h0 - y0  h1 = h1 + y0
        local y1 = h1 + 3 * 2 ^ 83  - 3 * 2 ^ 83   h1 = h1 - y1  h2 = h2 + y1
        local y2 = h2 + 3 * 2 ^ 101 - 3 * 2 ^ 101  h2 = h2 - y2  h3 = h3 + y2
        local y3 = h3 + 3 * 2 ^ 115 - 3 * 2 ^ 115  h3 = h3 - y3  h4 = h4 + y3
        local y4 = h4 + 3 * 2 ^ 133 - 3 * 2 ^ 133  h4 = h4 - y4  h5 = h5 + y4
        local y5 = h5 + 3 * 2 ^ 147 - 3 * 2 ^ 147  h5 = h5 - y5  h6 = h6 + y5
        local y6 = h6 + 3 * 2 ^ 163 - 3 * 2 ^ 163  h6 = h6 - y6  h7 = h7 + y6
        local y7 = h7 + 3 * 2 ^ 181 - 3 * 2 ^ 181  h7 = h7 - y7

        -- Reduce carry overflow into first limb.
        h0 = h0 + 5 / 2 ^ 130 * y7
    end

    -- Carry canonically.
    local c0 = h0 % 2 ^ 16   h1 = h0 - c0 + h1
    local c1 = h1 % 2 ^ 32   h2 = h1 - c1 + h2
    local c2 = h2 % 2 ^ 48   h3 = h2 - c2 + h3
    local c3 = h3 % 2 ^ 64   h4 = h3 - c3 + h4
    local c4 = h4 % 2 ^ 80   h5 = h4 - c4 + h5
    local c5 = h5 % 2 ^ 96   h6 = h5 - c5 + h6
    local c6 = h6 % 2 ^ 112  h7 = h6 - c6 + h7
    local c7 = h7 % 2 ^ 130

    -- Reduce carry overflow.
    h0 = c0 + 5 / 2 ^ 130 * (h7 - c7)
    c0 = h0 % 2 ^ 16
    c1 = h0 - c0 + c1

    -- Canonicalize.
    if      c7 == 0x3ffff * 2 ^ 112
        and c6 == 0xffff * 2 ^ 96
        and c5 == 0xffff * 2 ^ 80
        and c4 == 0xffff * 2 ^ 64
        and c3 == 0xffff * 2 ^ 48
        and c2 == 0xffff * 2 ^ 32
        and c1 == 0xffff * 2 ^ 16
        and c0 >= 0xfffb
    then
        c7, c6, c5, c4, c3, c2, c1, c0 = 0, 0, 0, 0, 0, 0, 0, c0 - 0xfffb
    end

    -- Decode s.
    local s0, s1, s2, s3 = u4x4(fmt4x4, key, 17)

    -- Add.
    local t0 =           s0          + c0 + c1  local u0 = t0 % 2 ^ 32
    local t1 = t0 - u0 + s1 * 2 ^ 32 + c2 + c3  local u1 = t1 % 2 ^ 64
    local t2 = t1 - u1 + s2 * 2 ^ 64 + c4 + c5  local u2 = t2 % 2 ^ 96
    local t3 = t2 - u2 + s3 * 2 ^ 96 + c6 + c7  local u3 = t3 % 2 ^ 128

    -- Encode.
    return p4x4(fmt4x4, u0, u1 / 2 ^ 32, u2 / 2 ^ 64, u3 / 2 ^ 96)
end

return {
    mac = mac,
}
]=],
["ccryptolib.random"]=[=[local expect = require "cc.expect".expect
local hw = require "ccryptolib.internal.hw"
local blake3 = require "ccryptolib.blake3"
local chacha20 = require "ccryptolib.chacha20"
local util = require "ccryptolib.internal.util"

local lassert = util.lassert

-- Extract local context.
local ctx = {
    "ccryptolib 2023-04-11T19:43Z random.lua initialization context",
    os.epoch("utc"),
    os.day(),
    os.time(),
    math.random(0, 2 ^ 24 - 1),
    math.random(0, 2 ^ 24 - 1),
    tostring({}),
    tostring({}),
}

local state = blake3.digest(table.concat(ctx, "|"))
local initialized = false
local hwMixed = false

--- Mixes random bytes taken by the last compatible peripheral we interacted
--- with, if any.
---
--- takeSeed resets to nil once called, so this function only does work when a
--- peripheral is (re-)attached.
local function mixHwSeed()
    local seed = hw.takeSeed()
    if not seed then return end
    state = blake3.digestKeyed(state, seed)
    hwMixed = true
end

--- Mixes entropy into the generator, and marks it as initialized.
--- @param seed string The seed data.
local function init(seed)
    expect(1, seed, "string")
    mixHwSeed()
    state = blake3.digestKeyed(state, seed)
    initialized = true
end

--- Returns whether the generator has been initialized or not.
--- @return boolean
local function isInit()
    return initialized
end

--- Initializes the generator using VM instruction timing noise.
---
--- This function counts how many instructions the VM can execute within a single
--- millisecond, and mixes the lower bits of these values into the generator state.
--- The current implementation collects data for 512 ms and takes the lower 8 bits from
--- each count.
---
--- Compared to fetching entropy from a trusted web source, this approach is riskier but
--- more convenient. The factors that influence instruction timing suggest that this
--- seed is unpredictable for other players, but this assumption might turn out to be
--- untrue.
local function initWithTiming()
    mixHwSeed()
    assert(os.epoch("utc") ~= 0)

    local f = assert(load("local e=os.epoch return{" .. ("e'utc',"):rep(256) .. "}"))

    do -- Warmup.
        local t = f()
        while t[256] - t[1] > 1 do t = f() end
    end

    -- Fill up the buffer.
    local buf = {}
    for i = 1, 512 do
        local t = f()
        while t[256] == t[1] do t = f() end
        for j = 1, 256 do
            if t[j] ~= t[1] then
                buf[i] = j - 1
                break
            end
        end
    end

    -- Perform a histogram check to catch faulty os.epoch implementations.
    local hist = {}
    for i = 0, 255 do hist[i] = 0 end
    for i = 1, #buf do hist[buf[i]] = hist[buf[i]] + 1 end
    for i = 0, 255 do assert(hist[i] < 20) end

    init(string.char(table.unpack(buf)))
end

--- Initializes the generator with the best available source.
---
--- If no other sources are found, this function will resort to `initWithTiming`
--- as entropy source. See its documentation for security caveats.
local function initAuto()
    mixHwSeed()
    if not hwMixed then initWithTiming() end
    initialized = true
end

--- Mixes extra entropy into the generator state.
--- @param data string The additional entropy to mix.
local function mix(data)
    expect(1, data, "string")
    mixHwSeed()
    state = blake3.digestKeyed(state, data)
end

--- Generates random bytes.
--- @param len number The desired output length.
--- @return string bytes
local function random(len)
    len = math.max(0, expect(1, len, "number"))
    lassert(initialized, "attempt to use an uninitialized random generator", 2)
    local ok, out = hw.random(len)
    if ok then return out end

    mixHwSeed()
    local msg = ("\0"):rep(len + 32)
    local nonce = ("\0"):rep(12)
    local out = chacha20.crypt(state, nonce, msg, 8, 0)
    state = out:sub(1, 32)
    return out:sub(33)
end

return {
    init = init,
    isInit = isInit,
    initWithTiming = initWithTiming,
    initAuto = initAuto,
    mix = mix,
    random = random,
}
]=],
["ccryptolib.sha256"]=[=[--- The SHA256 cryptographic hash function.

local expect = require "cc.expect".expect
local lassert = require "ccryptolib.internal.util".lassert
local packing = require "ccryptolib.internal.packing"
local hw = require "ccryptolib.internal.hw"

local rol = bit32.lrotate
local shr = bit32.rshift
local bxor = bit32.bxor
local bnot = bit32.bnot
local band = bit32.band
local unpack = unpack or table.unpack
local p1x8, fmt1x8 = packing.compilePack(">I8")
local p16x4, fmt16x4 = packing.compilePack(">I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4")
local u16x4 = packing.compileUnpack(fmt16x4)
local p8x4, fmt8x4 = packing.compilePack(">I4I4I4I4I4I4I4I4")
local u8x4 = packing.compileUnpack(fmt8x4)

local function primes(n, exp)
    local out = {}
    local p = 2
    for i = 1, n do
        out[i] = bxor(p ^ exp % 1 * 2 ^ 32)
        repeat p = p + 1 until 2 ^ p % p == 2
    end
    return out
end

local K = primes(64, 1 / 3)

local h0 = primes(8, 1 / 2)

local function compress(h, w)
    local h0, h1, h2, h3, h4, h5, h6, h7 = unpack(h)
    local K = K

    -- Message schedule.
    for j = 17, 64 do
        local wf = w[j - 15]
        local w2 = w[j - 2]
        local s0 = bxor(rol(wf, 25), rol(wf, 14), shr(wf, 3))
        local s1 = bxor(rol(w2, 15), rol(w2, 13), shr(w2, 10))
        w[j] = w[j - 16] + s0 + w[j - 7] + s1
    end

    -- Block.
    local a, b, c, d, e, f, g, h = h0, h1, h2, h3, h4, h5, h6, h7
    for j = 1, 64 do
        local s1 = bxor(rol(e, 26), rol(e, 21), rol(e, 7))
        local ch = bxor(band(e, f), band(bnot(e), g))
        local temp1 = h + s1 + ch + K[j] + w[j]
        local s0 = bxor(rol(a, 30), rol(a, 19), rol(a, 10))
        local maj = bxor(band(a, b), band(a, c), band(b, c))
        local temp2 = s0 + maj

        h = g
        g = f
        f = e
        e = d + temp1
        d = c
        c = b
        b = a
        a = temp1 + temp2
    end

    return {
        (h0 + a) % 2 ^ 32,
        (h1 + b) % 2 ^ 32,
        (h2 + c) % 2 ^ 32,
        (h3 + d) % 2 ^ 32,
        (h4 + e) % 2 ^ 32,
        (h5 + f) % 2 ^ 32,
        (h6 + g) % 2 ^ 32,
        (h7 + h) % 2 ^ 32,
    }
end

--- Hashes data using SHA256.
--- @param data string Input bytes.
--- @return string hash The 32-byte hash value.
local function digest(data)
    expect(1, data, "string")
    local ok, out = hw.sha256(data)
    if ok then return out end

    -- Pad input.
    local bitlen = #data * 8
    local padlen = -(#data + 9) % 64
    data = data .. "\x80" .. ("\0"):rep(padlen) .. p1x8(fmt1x8, bitlen)

    -- Digest.
    local h = h0
    for i = 1, #data, 64 do
        h = compress(h, {u16x4(fmt16x4, data, i)})
    end

    return p8x4(fmt8x4, unpack(h))
end

-- Reports once every ~10ms on a standard CCEmuX emulator.
local PBKDF2_CB_ITERATIONS = 50

--- Hashes a password using PBKDF2-HMAC-SHA256.
--- @param password string The password to hash.
--- @param salt string The password's salt.
--- @param iter number The number of iterations to perform.
--- @param progress fun(iter: number)? An optional function to periodically call with the current iteration number as argument.
--- @return string dk The 32-byte derived key.
local function pbkdf2(password, salt, iter, progress)
    expect(1, password, "string")
    expect(2, salt, "string")
    expect(3, iter, "number")
    lassert(iter % 1 == 0, "iteration number must be an integer", 2)
    lassert(iter > 0, "iteration number must be positive", 2)
    expect(4, progress, "function", "nil")

    -- Pad password.
    if #password > 64 then password = digest(password) end
    password = {u16x4(fmt16x4, password .. ("\0"):rep(64), 1)}

    -- Compute password blocks.
    local ikp = {}
    local okp = {}
    for i = 1, 16 do
        ikp[i] = bxor(password[i], 0x36363636)
        okp[i] = bxor(password[i], 0x5c5c5c5c)
    end

    local hikp = compress(h0, ikp)
    local hokp = compress(h0, okp)

    -- 96-byte padding.
    local pad96 = {2 ^ 31, 0, 0, 0, 0, 0, 0, 0x300}

    -- First iteration.
    local pre = p16x4(fmt16x4, unpack(ikp))
    local hs = {u8x4(fmt8x4, digest(pre .. salt .. "\0\0\0\1"), 1)}
    for i = 1, 8 do hs[i + 8] = pad96[i] end
    hs = compress(hokp, hs)

    -- Second iteration onwards.
    local out = {unpack(hs)}
    for i = 2, iter do
        for j = 1, 8 do hs[j + 8] = pad96[j] end
        hs = compress(hikp, hs)
        for j = 1, 8 do hs[j + 8] = pad96[j] end
        hs = compress(hokp, hs)
        for j = 1, 8 do out[j] = bxor(out[j], hs[j]) end
        if progress and i % PBKDF2_CB_ITERATIONS == 0 then progress(i) end
    end

    return p8x4(fmt8x4, unpack(out))
end

return {
    digest = digest,
    pbkdf2 = pbkdf2,
}
]=],
["ccryptolib.util"]=[=[--- General utilities for handling byte strings.

local expect = require "cc.expect".expect
local random = require "ccryptolib.random"
local poly1305 = require "ccryptolib.poly1305"

--- Returns the hexadecimal version of a string.
--- @param str string A string.
--- @return string hex The hexadecimal version of the string.
local function toHex(str)
    expect(1, str, "string")
    return ("%02x"):rep(#str):format(str:byte(1, -1))
end

--- Converts back a string from hexadecimal.
--- @param hex string A hexadecimal string.
--- @return string? str The original string, or nil if the input is invalid.
local function fromHex(hex)
    expect(1, hex, "string")
    local out = {}
    local n = 0
    for c in hex:gmatch("%x%x") do
        n = n + 1
        out[n] = tonumber(c, 16)
    end
    if 2 * n == #hex then return string.char(table.unpack(out)) end
end

--- Compares two strings while mitigating secret leakage through timing.
--- @param a string
--- @param b string
--- @return boolean eq Whether a == b.
local function compare(a, b)
    expect(1, a, "string")
    expect(2, b, "string")
    if #a ~= #b then return false end
    local kaux = random.random(32)
    return poly1305.mac(kaux, a) == poly1305.mac(kaux, b)
end

return {
    toHex = toHex,
    fromHex = fromHex,
    compare = compare,
}
]=],
["ccryptolib.x25519"]=[=[--- The X25519 key exchange scheme.

local expect = require "cc.expect".expect
local lassert = require "ccryptolib.internal.util".lassert
local hw = require "ccryptolib.internal.hw"
local util = require "ccryptolib.internal.util"
local c25 = require "ccryptolib.internal.curve25519"

--- Computes the public key from a secret key.
--- @param sk string A random 32-byte secret key.
--- @return string pk The matching public key.
local function publicKey(sk)
    expect(1, sk, "string")
    assert(#sk == 32, "secret key length must be 32")
    local ok, out = hw.x25519PublicKey(sk)
    if ok then return out end
    return c25.encode(c25.scale(c25.mulG(util.bits(sk))))
end

--- Performs the key exchange.
--- @param sk string A Curve25519 secret key.
--- @param pk string A public key, usually derived from someone else's secret key.
--- @return string ss The 32-byte shared secret between both keys.
local function exchange(sk, pk)
    expect(1, sk, "string")
    lassert(#sk == 32, "secret key length must be 32", 2)
    expect(2, pk, "string")
    lassert(#pk == 32, "public key length must be 32", 2) --- @cast pk String32
    local ok, out = hw.x25519Exchange(sk, pk)
    if ok then return out end
    return c25.encode(c25.scale(c25.ladder8(c25.decode(pk), util.bits8(sk))))
end

return {
    publicKey = publicKey,
    exchange = exchange,
}
]=],
["ccryptolib.x25519c"]=[=[local expect = require "cc.expect".expect
local lassert = require "ccryptolib.internal.util".lassert
local hw = require "ccryptolib.internal.hw"
local fq = require "ccryptolib.internal.fq"
local fp = require "ccryptolib.internal.fp"
local c25 = require "ccryptolib.internal.curve25519"
local sha512 = require "ccryptolib.internal.sha512"
local random = require "ccryptolib.random"

--- Masks an exchange secret key.
--- @param sk string A random 32-byte Curve25519 secret key.
--- @return string msk A masked secret key.
local function mask(sk)
    expect(1, sk, "string")
    lassert(#sk == 32, "secret key length must be 32", 2)
    local mask = random.random(32)
    local x = fq.decodeClamped(sk)
    local r = fq.decodeClamped(mask)
    local xr = fq.sub(x, r)
    return fq.encode(xr) .. mask
end

--- Masks a signature secret key.
--- @param sk string A random 32-byte Edwards25519 secret key.
--- @return string msk A masked secret key.
local function maskS(sk)
    expect(1, sk, "string")
    lassert(#sk == 32, "secret key length must be 32", 2)
    return mask(sha512.digest(sk):sub(1, 32))
end

--- Rerandomizes the masking on a masked key.
--- @param msk string A masked secret key.
--- @return string msk The same secret key, but with another mask.
local function remask(msk)
    expect(1, msk, "string")
    lassert(#msk == 64, "masked secret key length must be 64", 2)
    local newMask = random.random(32)
    local xr = fq.decode(msk:sub(1, 32))
    local r = fq.decodeClamped(msk:sub(33))
    local s = fq.decodeClamped(newMask)
    local xs = fq.add(xr, fq.sub(r, s))
    return fq.encode(xs) .. newMask
end

--- Returns the ephemeral exchange secret key of this masked key.
--- This is the second secret key in the "double key exchange" in @{exchange},
--- the first being the key that has been masked. The ephemeral key changes
--- every time @{remask} is called.
--- @param msk string A masked secret key.
--- @return string esk The ephemeral half of the masked secret key.
local function ephemeralSk(msk)
    expect(1, msk, "string")
    lassert(#msk == 64, "masked secret key length must be 64", 2)
    return msk:sub(33)
end

-- This does not have the same behavior on twist points. It is technically
-- inconsistent, but those points are all adversarial inputs, so we may as well
-- not care.
local function hwExchangeOnPoint(sk, P)
    local ok1, rP = hw.x25519Exchange(sk:sub(33), P)
    if not ok1 then return end

    local xr = fq.decode(sk:sub(1, 32))
    local r = fq.decodeClamped(sk:sub(33))
    local x = fq.add(xr, r)

    local ok2, xP = hw.x25519Exchange(fq.encodeClamped(x), P)
    if not ok2 then return end

    return true, xP, rP
end

local function exchangeOnPoint(sk, P)
    local xr = fq.decode(sk:sub(1, 32))
    local r = fq.decodeClamped(sk:sub(33))
    local rP, xrP, dP = c25.prac(P, fq.makeRuleset(fq.eighth(r), fq.eighth(xr)))

    -- Return early if P has small order or if r = xr. (1)
    if not rP then
        local out = fp.encode(fp.num(0))
        return out, out
    end

    local xP = c25.dadd(dP, rP, xrP)

    -- Extract coordinates for scaling.
    local Px, Pz = P[1], P[2]
    local xPx, xPz = xP[1], xP[2]
    local rPx, rPz = rP[1], rP[2]

    -- Ensure all Z coordinates are squares.
    Px, Pz = fp.mul(Px, Pz), fp.square(Pz)
    xPx, xPz = fp.mul(xPx, xPz), fp.square(xPz)
    rPx, rPz = fp.mul(rPx, rPz), fp.square(rPz)

    -- We're splitting the secret x into (x - r (mod q), r). The multiplication
    -- adds them back together, but this only works if P's order is q, which is
    -- not the case on the twist.
    -- As a result, we need to check if P is on the twist and return 0 so as to
    -- not leak part of x. We do this by checking the curve equation against P.
    -- The projective equation for curve25519 is Y²Z = X(X² + AXZ + Z²). Since Z
    -- is a square, checking validity means checking the right-hand side to be a
    -- square.
    local Px2 = fp.square(Px)
    local Pz2 = fp.square(Pz)
    local Pxz = fp.mul(Px, Pz)
    local APxz = fp.kmul(Pxz, 486662)
    local rhs = fp.mul(Px, fp.add(Px2, fp.carry(fp.add(APxz, Pz2))))

    -- Find the square root of 1 / (rhs * xPz * rPz).
    -- Neither rPz, xPz, nor rhs are 0:
    -- - If rhs was 0, then P would be low order, which would return at (1).
    -- - Since P isn't low order, clamping prevents the ladder from returning O.
    -- Since we've just squared both xPz and rPz, the root will exist iff rhs is
    -- a square. This checks the curve equation, so we're done.
    local root = fp.sqrtDiv(fp.num(1), fp.mul(fp.mul(xPz, rPz), rhs))
    if not root then
        local out = fp.encode(fp.num(0))
        return out, out
    end

    -- Get the inverses of both Z values.
    local xPzrPzInv = fp.mul(fp.square(root), rhs)
    local xPzInv = fp.mul(xPzrPzInv, rPz)
    local rPzInv = fp.mul(xPzrPzInv, xPz)

    -- Finish scaling and encode the output.
    return fp.encode(fp.mul(xPx, xPzInv)), fp.encode(fp.mul(rPx, rPzInv))
end

local G_ENC = fp.encode(fp.num(9))

--- Returns the X25519 public key of this masked key.
--- @param msk string A masked secret key.
local function publicKey(msk)
    expect(1, msk, "string")
    lassert(#msk == 64, "masked secret key length must be 64", 2)
    local ok, xP, rP = hwExchangeOnPoint(msk, G_ENC)
    if ok then return xP, rP end
    return (exchangeOnPoint(msk, c25.G))
end

--- Performs a double key exchange.
---
--- Returns 0 if the input public key has small order or if it isn't in the base
--- curve. This is different from standard X25519, which performs the exchange
--- even on the twist.
---
--- May incorrectly return 0 with negligible chance if the mask happens to match
--- the masked key. I haven't checked if clamping prevents that from happening.
---
--- @param sk string A masked secret key.
--- @param pk string An X25519 public key.
--- @return string sss The shared secret between the public key and the static half of the masked key.
--- @return string sse The shared secret betwen the public key and the ephemeral half of the masked key.
local function exchange(sk, pk)
    expect(1, sk, "string")
    lassert(#sk == 64, "masked secret key length must be 64", 2)
    expect(2, pk, "string")
    lassert(#pk == 32, "public key length must be 32", 2) --- @cast pk String32
    local ok, xP, rP = hwExchangeOnPoint(sk, pk)
    if ok then return xP, rP end
    return exchangeOnPoint(sk, c25.decode(pk))
end

return {
    mask = mask,
    remask = remask,
    publicKey = publicKey,
    ephemeralSk = ephemeralSk,
    exchange = exchange,
}
]=],
}
local nativeRequire=require
local cache={}
local function bundled(name)
 if cache[name] then return cache[name] end
 if not SOURCES[name] then return nativeRequire(name) end
 local env=setmetatable({require=bundled},{__index=_ENV})
 -- Assigned below: local function self-reference is supplied explicitly.
 env.require=bundled
 local value=assert(load(SOURCES[name],name,'t',env))()
 cache[name]=value; return value
end
local require=bundled
local PUBLIC_KEY="c2ea2e4f1f08cc3c712728e5685dc5220dad0dc0c1f06cc5d7756aa25caa1008"
local GITHUB_URL=""
local PASTEBIN_PARTS={}
local OFFLINE_ENVELOPE=textutils.unserializeJSON([=[{"payload":"{\"format\":1,\"version\":\"0.2.0\",\"sequence\":2,\"files\":{\"lantern/browser.lua\":{\"content\":\"local C=require('lantern.config')\\nlocal U=require('lantern.util')\\nlocal P=require('lantern.page')\\nlocal UI=require('lantern.ui')\\nlocal M={}\\nfunction M.parse(url)\\n  if url=='home' or url=='ln://home' then return 'home','/' end\\n  assert(not url:find('://',1,true) or url:match('^ln://') or url:match('^hub://'),'Unknown address scheme')\\n  local authority,path=url:match('^%a+://([^/]+)(/.*)$')\\n  if not authority then authority=url:match('^%a+://([^/]+)$') or url; path='/' end\\n  if authority:sub(1,1)=='@' then authority=authority:sub(2)..'=' end\\n  assert(U.slug(authority) or U.address(authority),'Invalid site address')\\n  return authority,assert(U.path(path))\\nend\\nfunction M.url(address,path)\\n  if U.address(address) then address='@'..address:sub(1,-2) end\\n  return 'ln://'..address..(path or '/')\\nend\\nfunction M.run(initial,preview)\\n  local tabs,current={{id=1,url='home',trail={},position=0,values={},scroll=0,focus=0,generation=0}},1\\n  local nextTabId=1\\n  local bookmarks=U.load(C.data..'/bookmarks.json',{}); local history=U.load(C.data..'/history.json',{})\\n  local N,networkError; local ok,value=pcall(function() return require('lantern.network').new(require('lantern.security').identity('browser')) end)\\n  if ok then N=value; N.discover() else networkError=tostring(value) end\\n  local jobs={}; local results={}; local running=true;local nextAction\\n  local function tab() return tabs[current] end\\n  local function home()\\n    local children={P.heading{text='Welcome to Lantern'},P.paragraph{text='A little web between worlds.'},P.link{text='Explore the Hub',href='hub://directory/'},P.link{text='Help and reference',href='hub://help/'},P.link{text='The Lantern showcase',href='hub://showcase/'},P.link{text='Your bookmarks',href='ln://bookmarks/'},P.link{text='Recently visited',href='ln://history/'}}\\n    if N then\\n      local names={}; for name in pairs(N.discovered) do names[#names+1]=name end; table.sort(names)\\n      children[#children+1]=P.link{text='Nearby websites ('..#names..')',href='ln://nearby/'}\\n    end\\n    return P.document('Home',children)\\n  end\\n  local function cachePath(url) if url:match('^hub://') then local ok,origin=pcall(require('lantern.cloud').origin); url=(ok and origin or 'unconfigured')..'|'..url end; return C.data..'/cache/'..U.hex(require('ccryptolib.sha256').digest(url))..'.json' end\\n  local function localPage(t)\\n    if t.url=='home' or t.url=='ln://home' then return home() end\\n    if t.url:match('^ln://nearby/') then\\n      local ok,page=pcall(function() local _,path=M.parse(t.url);return require('lantern.directory').nearby(N and N.discovered or {},path) end)\\n      return ok and page or P.document('Invalid directory page',{P.paragraph{text=tostring(page)},P.link{text='Nearby websites',href='ln://nearby/'}})\\n    end\\n    if t.url=='ln://bookmarks/' or t.url=='ln://history/' then\\n      local list=t.url=='ln://bookmarks/' and bookmarks or history; local children={}\\n      for i=#list,1,-1 do children[#children+1]=P.link{text=list[i],href=list[i]} end\\n      return P.document(t.url=='ln://bookmarks/' and 'Bookmarks' or 'History',children)\\n    end\\n  end\\n  local function navigate(url,method,fields,noTrail,redirects)\\n    local t=tab(); t.values={}; t.generation=t.generation+1; t.url=url; t.scroll=0; t.focus=0; t.error=nil; t.cached=false; t.address=nil; t.cloud=url:match('^hub://')~=nil\\n    if not noTrail then while #t.trail>t.position do table.remove(t.trail) end; t.trail[#t.trail+1]=url; t.position=#t.trail end\\n    t.page=localPage(t)\\n    if t.page then t.loading=false; return end\\n    if preview then\\n      local _,path=M.parse(url); local success,response=pcall(preview,{path=path,method=method or 'GET',fields=fields or {},id='preview'})\\n      if success and response.kind=='page' then t.page=response.page elseif success and response.kind=='redirect' and (redirects or 0)<5 then return navigate(M.url('preview',response.path),'GET',nil,true,(redirects or 0)+1) else t.error=success and response.message or tostring(response) end\\n      return\\n    end\\n    t.loading=true\\n    jobs[t]={tabId=t.id,url=url,method=method,fields=fields,generation=t.generation,redirects=redirects or 0}\\n    os.queueEvent('lantern_job'); os.queueEvent('lantern_cancel')\\n  end\\n  local function worker()\\n    while running do\\n      local target,job=next(jobs)\\n      if not job then os.pullEvent('lantern_job') else\\n        jobs[target]=nil\\n        local success,response,address=pcall(function()\\n          local alias,path=M.parse(job.url)\\n          if job.url:match('^hub://') then return require('lantern.cloud').get(alias,path,job.method,job.fields) end\\n          assert(N,networkError or 'Offline')\\n          return N.get(alias,path,job.method,job.fields,function() return target.generation~=job.generation end)\\n        end)\\n        results[#results+1]={job,success,response,address}\\n        os.queueEvent('lantern_result')\\n      end\\n    end\\n  end\\n  local rows,actions={},{ }\\n  local buttons={}\\n  local function draw()\\n    local t=tab(); local screen=UI.new(); buttons={}\\n    local function button(x,y,label,event)\\n      if x+#label-1>screen.w then return x end\\n      screen:text(x,y,label,'text','panel')\\n      buttons[#buttons+1]={x=x,y=y,width=#label,event=event}\\n      return x+#label+1\\n    end\\n    screen:bar(1,'panel');screen:text(2,1,'/\\\\\\\\ LANTERN','accent','panel')\\n    local x=15\\n    for i in ipairs(tabs) do\\n      local label=(i==current and '[' or ' ')..i..(i==current and ']' or ' ')\\n      if x+#label<=screen.w-5 then x=button(x,1,label,{tab=i}) end\\n    end\\n    button(screen.w-3,1,'[+]',{key=keys.t})\\n    screen:bar(2,'panel');x=2\\n    local controls=screen.w<40 and {{'<',keys.left},{'>',keys.right},{'Home',keys.h},{'Hub',keys.g},{'...',keys.m}} or {{'[<]',keys.left},{'[>]',keys.right},{'Home',keys.h},{'Hub',keys.g},{t.loading and 'Stop' or 'Reload',t.loading and keys.x or keys.r},{'Menu',keys.m}}\\n    for _,control in ipairs(controls) do x=button(x,2,control[1],{key=control[2]}) end\\n    screen:bar(3,'bg');screen:text(2,3,'['..(t.url=='home' and 'Click to enter an address' or t.url):sub(1,math.max(1,screen.w-4))..']','text')\\n    local status=t.cached and 'Offline saved page' or (preview and 'Local preview' or (t.cloud and 'Lantern Hub / HTTPS' or 'Local network'))\\n    if t.address then local alias=M.parse(t.url); local _,verified=require('lantern.security').check(alias,t.address); status=verified and 'Encrypted / verified identity' or 'Encrypted / identity unverified' end\\n    screen:text(2,4,(t.loading and 'Loading...  ' or 'Ready  ')..status,'muted')\\n    if t.error then t.page=P.document('Unable to load',{P.heading{text='Could not open this page'},P.paragraph{text=tostring(t.error):gsub('^.-:%d+: ','')},P.paragraph{text='Choose Reload to try again, or Hub to explore.'}}) end\\n    if not t.page then t.page=P.document('Loading',{P.heading{text='Opening your next little world...'}}) end\\n    rows,actions=P.layout(t.page,math.max(1,screen.w-2),t.values)\\n    t.scroll=math.min(t.scroll,math.max(0,#rows-(screen.h-6)))\\n    for y=6,screen.h-1 do\\n      local row=rows[y-5+t.scroll]\\n      local function drawRow(r,left,width)\\n        if not r then return end\\n        if r.pixels then screen:pixels(left,y,r.pixels)\\n        else\\n          local selected=r.action and r.action.index==t.focus\\n          local text=(selected and '> ' or '')..(r.text or '')\\n          screen:text(left,y,text:sub(1,width),selected and 'bg' or r.style,selected and 'accent' or 'bg')\\n        end\\n      end\\n      if row and row.spans then for _,span in ipairs(row.spans) do drawRow(span.row,span.x+1,span.width) end else drawRow(row,2,screen.w-2) end\\n    end\\n    screen:footer(screen.w<40 and 'Click or Tab / Enter' or 'Click to explore   Tab / Enter   Scroll: arrows');screen:flush()\\n  end\\n  local function activate(a)\\n    if not a then return end\\n    local t=tab()\\n    if a.kind=='link' then\\n      if a.href:match('^ln://') or a.href:match('^hub://') then navigate(a.href) else local alias=M.parse(t.url); navigate(t.cloud and ('hub://'..alias..a.href) or M.url(alias,a.href)) end\\n    elseif a.kind=='input' then t.values[a.key]=UI.prompt(a.node.text or a.node.name,t.values[a.key],a.node.password and '*' or nil):sub(1,2048)\\n    elseif a.kind=='checkbox' then t.values[a.key]=not t.values[a.key]\\n    elseif a.kind=='select' then local i=UI.choose(a.node.text or a.node.name,a.node.options); if i then t.values[a.key]=a.node.options[i] end\\n    elseif a.kind=='submit' then\\n      local fields={}; local prefix=a.form.action..':'\\n      for k,v in pairs(t.values) do if k:sub(1,#prefix)==prefix then fields[k:sub(#prefix+1)]=v end end\\n      local alias=M.parse(t.url); navigate(t.cloud and ('hub://'..alias..a.form.action) or M.url(alias,a.form.action),'POST',fields)\\n    end\\n  end\\n  local function loop()\\n    navigate(initial or 'home'); draw()\\n    while running do\\n      local e={os.pullEvent()}; local t=tab()\\n      if N then N.observe(e) end\\n      if e[1]=='lantern_result' and #results>0 then\\n        local result=table.remove(results,1)\\n        local job,success,response,address=result[1],result[2],result[3],result[4]\\n        -- CC copies event tables: resolve the live tab by its stable ID.\\n        local target\\n        for _,candidate in ipairs(tabs) do if candidate.id==job.tabId then target=candidate; break end end\\n        if target and target.generation==job.generation then\\n          target.loading=false\\n          if success and response.kind=='page' then\\n            local valid,err=P.validate(response.page)\\n            if valid then\\n              target.page=response.page; target.address=address\\n              if not job.method or job.method=='GET' then\\n                pcall(function()\\n                  local root=C.data..'/cache'; fs.makeDir(root); local files=fs.list(root)\\n                  if #files>=16 then table.sort(files); fs.delete(root..'/'..files[1]) end\\n                  U.save(cachePath(job.url),{page=response.page,address=address})\\n                end)\\n              end\\n              history[#history+1]=job.url; while #history>100 do table.remove(history,1) end; U.save(C.data..'/history.json',history)\\n            else target.error=err end\\n          elseif success and response.kind=='redirect' and job.redirects<5 and U.path(response.path) then\\n            local alias=M.parse(job.url); local old=current\\n            for i,v in ipairs(tabs) do if v==target then current=i end end\\n            navigate(job.url:match('^hub://') and ('hub://'..alias..response.path) or M.url(alias,response.path),'GET',nil,true,job.redirects+1); current=old\\n          else\\n            local cached=(not job.method or job.method=='GET') and U.load(cachePath(job.url),nil)\\n            if cached and P.validate(cached.page) then target.page=cached.page; target.cached=true; target.address=nil\\n            else target.error=success and (response.message or 'Unexpected response') or tostring(response) end\\n          end\\n        end\\n      elseif e[1]=='key' then\\n        local k=e[2]\\n        if k==keys.q then running=false; return\\n        elseif k==keys.o then local url=UI.prompt('Address',t.url=='home' and '' or t.url); if url~='' then navigate(url) end\\n        elseif k==keys.h then navigate('home')\\n        elseif k==keys.g then navigate('hub://directory/')\\n        elseif k==keys.r then navigate(t.url,'GET',nil,true)\\n        elseif k==keys.x then t.generation=t.generation+1; jobs[t]=nil; t.loading=false; t.error='Cancelled'; os.queueEvent('lantern_cancel')\\n        elseif k==keys.d then if N then N.discover();navigate('ln://nearby/') else UI.message('Nearby sites unavailable',networkError or 'Attach a modem and import a unique seed for local networking.') end\\n        elseif k==keys.t and #tabs<8 then nextTabId=nextTabId+1; tabs[#tabs+1]={id=nextTabId,trail={},position=0,values={},scroll=0,focus=0,generation=0}; current=#tabs; navigate('home')\\n        elseif k==keys.n then current=current%#tabs+1\\n        elseif k==keys.w and #tabs>1 then t.generation=t.generation+1; jobs[t]=nil; table.remove(tabs,current); current=math.min(current,#tabs); os.queueEvent('lantern_cancel')\\n        elseif k==keys.b then if #bookmarks<100 then bookmarks[#bookmarks+1]=t.url; U.save(C.data..'/bookmarks.json',bookmarks) end\\n        elseif k==keys.left or k==keys.right then local pos=t.position+(k==keys.left and -1 or 1); if t.trail[pos] then t.position=pos; navigate(t.trail[pos],'GET',nil,true) end\\n        elseif k==keys.tab and #actions>0 then t.focus=t.focus%#actions+1; t.scroll=math.max(0,actions[t.focus].row-1)\\n        elseif k==keys.enter then activate(actions[t.focus])\\n        elseif k==keys.up then t.scroll=math.max(0,t.scroll-1)\\n        elseif k==keys.down then t.scroll=t.scroll+1\\n        elseif k==keys.pageDown then t.scroll=t.scroll+8 elseif k==keys.pageUp then t.scroll=math.max(0,t.scroll-8)\\n        elseif k==keys.f then\\n          local query=UI.prompt('Find in page'):lower(); if query~='' then for i,row in ipairs(rows) do if (row.text or ''):lower():find(query,1,true) then t.scroll=i-1; break end end end\\n        elseif k==keys.v and t.address then\\n          local entered=UI.prompt('Paste independently verified fingerprint')\\n          if entered==t.address then local alias=M.parse(t.url); require('lantern.security').pin(alias,t.address,true) end\\n        elseif k==keys.m then\\n          local options={'Open address','Home','Explore Hub','Discover nearby sites','Bookmarks','History','Find in page','Verify fingerprint','New tab','Next tab','Close tab','Quit'}\\n          local profile=U.load(C.root..'/current/release.json',{}).profile\\n          if not preview and (profile==2 or profile==3) and fs.exists(C.root..'/current/lantern/host.lua') then options[#options+1]='Host / manage local sites' end\\n          local updateIndex;if not preview then updateIndex=#options+1;options[updateIndex]='Check for updates' end\\n          local i=UI.choose('Browser menu',options)\\n          local mapping={keys.o,keys.h,keys.g,keys.d,false,false,keys.f,keys.v,keys.t,keys.n,keys.w,keys.q}\\n          if updateIndex and i==updateIndex then nextAction='update';running=false;return\\n          elseif i==13 then nextAction='host';running=false;return\\n          elseif i==5 then navigate('ln://bookmarks/') elseif i==6 then navigate('ln://history/') elseif i then os.queueEvent('key',mapping[i]) end\\n        end\\n      elseif e[1]=='mouse_scroll' then t.scroll=math.max(0,t.scroll+e[2]*3)\\n      elseif e[1]=='mouse_click' then\\n        local hit=false\\n        for _,button in ipairs(buttons) do\\n          if e[4]==button.y and e[3]>=button.x and e[3]<button.x+button.width then\\n            if button.event.tab then current=button.event.tab else os.queueEvent('key',button.event.key) end\\n            hit=true;break\\n          end\\n        end\\n        if not hit and e[4]==3 then os.queueEvent('key',keys.o)\\n        elseif not hit and e[4]>=6 and e[4]<select(2,term.getSize()) then\\n          local row=e[4]-5+t.scroll\\n          for _,a in ipairs(actions) do if a.row==row and (not a.x or (e[3]>=a.x+1 and e[3]<a.x+1+a.width)) then t.focus=a.index; activate(a); break end end\\n        end\\n      end\\n      if tab().url=='home' then tab().page=home() end\\n      if tab().url:match('^ln://nearby/') then tab().page=localPage(tab()) end\\n      draw()\\n      -- Dialogs may consume wake-up events; keep replies until the main loop applies them.\\n      if #results>0 then os.queueEvent('lantern_result') end\\n    end\\n  end\\n  if N then parallel.waitForAny(require('ecnet2').daemon,worker,loop) else parallel.waitForAny(worker,loop) end\\n  return nextAction\\nend\\nreturn M\\n\",\"size\":15515,\"sha256\":\"35c1547cbdd84cc6eb934261484cd7ec70d425b90f7ef5a9cd1750991acf36bb\"},\"lantern/cloud.lua\":{\"content\":\"local C=require('lantern.config')\\nlocal U=require('lantern.util')\\nlocal P=require('lantern.page')\\nlocal M={}\\nfunction M.origin()\\n  local origin=U.load(C.data..'/hub.json',{}).origin or C.hub\\n  assert(type(origin)=='string' and origin:match('^https://[%w%.%-]+$'),'Configure Lantern Hub: lantern hub https://your-hub.example')\\n  return origin\\nend\\nfunction M.configure(origin)\\n  assert(origin:match('^https://[%w%.%-]+$'),'Use an HTTPS origin without a path')\\n  U.save(C.data..'/hub.json',{origin=origin})\\nend\\nlocal function request(path,body,key)\\n  assert(http,'HTTP is disabled on this server. Local modem sites still work.')\\n  local headers={['Content-Type']='application/json'}\\n  if key then headers.Authorization='Bearer '..key end\\n  local handle,err,failed -- Never forward upload credentials through redirects\\n  local options={url=M.origin()..path,headers=headers,redirect=false,binary=true,timeout=20}\\n  if body then options.method='POST'; options.body=textutils.serializeJSON(body) else options.method='GET' end\\n  -- Synchronous wrappers yield; the browser invokes this in its separate request worker.\\n  if body then handle,err,failed=http.post(options) else handle,err,failed=http.get(options) end\\n  handle=handle or failed; assert(handle,err or 'Hub unreachable')\\n  local code=handle.getResponseCode(); local pieces,total={},0\\n  while true do local s=handle.read(8192); if not s then break end; total=total+#s; if total>262144 then handle.close(); error('Hub response too large') end; pieces[#pieces+1]=s end\\n  handle.close(); local result=assert(U.json(table.concat(pieces)))\\n  assert(code>=200 and code<300,result.error or result.message or 'Hub request rejected')\\n  return result\\nend\\nfunction M.get(site,path,method,fields)\\n  if site=='directory' then\\n    local D=require('lantern.directory');local page=D.number(path)\\n    local result=request('/api/sites?page='..page)\\n    return {kind='page',page=D.render('Lantern Hub',result.sites or {},page,result.hasMore==true,'hub://directory','Published on Lantern Hub. Available even when the original Minecraft host is offline.')}\\n  end\\n  assert(U.slug(site),'Invalid Hub site')\\n  -- HTTPS handles transport entropy. Request IDs are deduplication identifiers, not secrets.\\n  local id=tostring(os.getComputerID())..'-'..tostring(os.epoch('utc'))..'-'..tostring(math.random(100000,999999))\\n  return request('/api/sites/'..site..'/request',{method=method or 'GET',path=assert(U.path(path)),id=id,fields=fields or {}})\\nend\\nfunction M.upload(name)\\n  local root=require('lantern.sites').root(name); local files={}\\n  local function scan(dir,prefix)\\n    for _,file in ipairs(fs.list(dir)) do\\n      local relative=prefix..file\\n      if fs.isDir(dir..'/'..file) then if file~='data' then scan(dir..'/'..file,relative..'/') end\\n      elseif relative=='app.lua' or file:match('%.json$') then files[relative]=assert(U.read(dir..'/'..file,131072)) end\\n    end\\n  end\\n  scan(root,'')\\n  local secretPath=C.data..'/private/hub-'..name..'.json'; local saved=U.load(secretPath,{})\\n  local origin=M.origin(); local key=saved.origin==origin and saved.key\\n  if not key then print('Paste the upload key for '..name..' (hidden):'); key=read('*'); assert(key:match('^ln_%x+$') and #key==67,'Invalid upload key'); U.save(secretPath,{origin=origin,key=key}) end\\n  local result=request('/api/sites/'..name..'/versions',{format=1,files=files},key)\\n  print('Draft uploaded: '..result.version); print('Publish from '..origin..'/dashboard/'..name)\\n  return result\\nend\\nreturn M\\n\",\"size\":3500,\"sha256\":\"97bde60fd05d2088d3e6a8614bc339f5510c41bbc949f465572f3a7ea9351153\"},\"lantern/config.lua\":{\"content\":\"return {version='0.2.0', sequence=2, target='1.120.2', root='/lantern', data='/lantern-data',\\n  hub='https://lantern.thultz.dev', protocol='lantern/1', discovery=47631, maxDocument=131072, chunk=8192,\\n  timeout=12, maxConnections=12, maxNodes=512, maxDepth=12}\\n\",\"size\":261,\"sha256\":\"2ffe6c718829339d99ebd232a5c7659d738a67cb5b6889c4b2fbf6496fb19de0\"},\"lantern/directory.lua\":{\"content\":\"local P=require('lantern.page')\\nlocal U=require('lantern.util')\\nlocal M={size=20}\\nfunction M.number(path)\\n  if not path or path=='/' then return 0 end\\n  local n=tonumber(path:match('^/page/(%d+)$'))\\n  assert(n and n<=1000,'Invalid directory page');return n\\nend\\nfunction M.render(title,entries,page,more,base,note)\\n  local children={P.heading{text=title},P.paragraph{text='Page '..(page+1)..' / up to '..M.size..' sites per page'},P.paragraph{text=note}}\\n  local function controls()\\n    if page>0 then children[#children+1]=P.link{text='Previous page',href=base..'/page/'..(page-1)} end\\n    if more then children[#children+1]=P.link{text='Next page',href=base..'/page/'..(page+1)} end\\n  end\\n  controls()\\n  if #entries==0 then children[#children+1]=P.paragraph{text='No sites on this page. Refresh discovery or return to the previous page.'} end\\n  for i=1,math.min(#entries,M.size) do\\n    local s=entries[i]\\n    if U.slug(s.slug) then\\n      children[#children+1]=P.link{text=U.clean(s.title or s.slug):sub(1,120),href=(s.localSite and 'ln://' or 'hub://')..s.slug..'/'}\\n      if s.description and s.description~='' then children[#children+1]=P.paragraph{text=U.clean(s.description):sub(1,300)} end\\n    end\\n  end\\n  controls();return P.document(title,children)\\nend\\nfunction M.nearby(discovered,path)\\n  local page=M.number(path);local names={};for name in pairs(discovered) do names[#names+1]=name end;table.sort(names)\\n  local entries={};for i=page*M.size+1,math.min(#names,(page+1)*M.size) do entries[#entries+1]={slug=names[i],localSite=true} end\\n  return M.render('Nearby websites',entries,page,#names>(page+1)*M.size,'ln://nearby',#names..' remembered sites. Discovery is an untrusted hint; verify fingerprints. Press D to refresh. The discovery cache holds up to 512 names.')\\nend\\nreturn M\\n\",\"size\":1790,\"sha256\":\"6bed17085ba0d3be68b3e8745c49935d7cd7769316028601020a2ed501586edc\"},\"lantern/editor.lua\":{\"content\":\"local U=require('lantern.util')\\nlocal UI=require('lantern.ui')\\nlocal S=require('lantern.sites')\\nlocal M={}\\nfunction M.run(name,create)\\n  if not name then\\n    local sites=S.list(); local choices={'Create a new site'}; for _,s in ipairs(sites) do choices[#choices+1]=s end\\n    local pick=create and 1 or UI.choose('Your websites',choices); if not pick then return end\\n    if pick==1 then\\n      name=UI.prompt('Site name (lowercase letters/numbers/hyphens)')\\n      local templates={'personal','documentation','directory','guestbook','showcase','fieldnotes'}\\n      local template=UI.choose('Choose a starter',templates); if not template then return end\\n      S.create(name,templates[template])\\n    else name=sites[pick-1] end\\n  end\\n  local root=S.root(name)\\n  while true do\\n    local i=UI.choose('Edit / '..name,{'Edit page JSON','Edit server Lua','Validate site','Preview locally','Start hosting','Upload Hub draft','Back'})\\n    if not i or i==7 then return end\\n    local success,failure=pcall(function()\\n    if i==1 then\\n      local path=UI.prompt('Page path','/index.json'); path=assert(U.path(path)); assert(path:match('%.json$'),'Pages use .json')\\n      shell.run('edit',root..path); UI.last=nil\\n    elseif i==2 then\\n      if not fs.exists(root..'/app.lua') then\\n        U.write(root..'/app.lua',[[-- Return page data; print() writes to the host console.\\nreturn function(ctx)\\n  local P = ctx.page\\n  return {kind=\\\"page\\\", page=P.document(\\\"My website\\\", {\\n    P.heading{text=\\\"Hello!\\\"},\\n    P.paragraph{text=\\\"testing\\\"}\\n  })}\\nend\\n]])\\n      end\\n      UI.message('SERVER LUA / How it works','app.lua must return function(ctx).\\\\nReturn a page built with ctx.page to show text. print() only writes to the host console.\\\\nExisting code is preserved. New files start with a working example.')\\n      shell.run('edit',root..'/app.lua'); UI.last=nil\\n    elseif i==3 then\\n      local errors={}\\n      local function scan(dir)\\n        for _,file in ipairs(fs.list(dir)) do local path=dir..'/'..file\\n          if fs.isDir(path) then if file~='data' then scan(path) end\\n          elseif file:match('%.json$') then local p=U.load(path,{}); local ok,err=require('lantern.page').validate(p); if not ok then errors[#errors+1]=file..': '..err end end\\n        end\\n      end\\n      scan(root)\\n      local ok,err=pcall(function() S.handler(name)({method='GET',path='/',fields={},id='validation'}) end)\\n      if not ok then errors[#errors+1]='Server Lua / home page: '..tostring(err) end\\n      UI.message(#errors==0 and 'Validation passed' or 'Fix these issues',#errors==0 and 'JSON pages and the home-page GET passed. Other Lua routes and form submissions still need testing.' or table.concat(errors,'\\\\n'))\\n    elseif i==4 then require('lantern.browser').run('ln://preview/',S.handler(name))\\n    elseif i==5 then require('lantern.host').run(name)\\n    elseif i==6 then require('lantern.cloud').upload(name); UI.prompt('Draft uploaded. Publish from your Hub dashboard. Enter to continue.') end\\n    end)\\n    if not success then\\n      if tostring(failure)=='Terminated' then error(failure,0) end\\n      UI.message('Could not complete action',tostring(failure)..'\\\\nYour source files are preserved. Choose Edit server Lua or Edit page JSON to fix the issue.')\\n    end\\n  end\\nend\\nreturn M\\n\",\"size\":3251,\"sha256\":\"2da38dae3a5e6f778a8bb799914915b39808adc2ff3651f50a6545a5531fa296\"},\"lantern/host.lua\":{\"content\":\"local C=require('lantern.config')\\nlocal U=require('lantern.util')\\nlocal M={}\\nfunction M.dashboard()\\n  local UI=require('lantern.ui'); local S=require('lantern.sites')\\n  while true do\\n    local sites=S.list()\\n    local choices={'Create a site from a template','Hosting setup / help'}\\n    for _,name in ipairs(sites) do choices[#choices+1]='Manage / '..name end\\n    choices[#choices+1]='Exit to shell'\\n    local pick=UI.choose('SERVER TOOLS / '..#sites..' local sites',choices)\\n    if not pick or pick==#choices then return end\\n    if pick==1 then require('lantern.editor').run(nil,true)\\n    elseif pick==2 then\\n      local help=UI.choose('HOSTING / Choose a topic',{'Create and edit a site','Serve nearby computers','Publish to the global Hub','Back'})\\n      local lines\\n      if help==1 then lines={'Choose Create a site, then a starter.','Edit JSON pages or a Lua handler.','Preview locally needs no modem.','Files: /lantern-data/sites/<name>','This is source editing, not drag/drop.'}\\n      elseif help==2 then lines={'Attach a modem to this computer.','Import a unique desktop security seed:','lantern seed <seed-file>','Manage a site, then Start hosting.','Keep the computer and chunk running.','Visitors open ln://<site-name>/'}\\n      elseif help==3 then lines={'Create the same address on the Hub.','Manage your local site: Upload Hub draft.','Use the site upload key from dashboard.','Publish the draft on lantern.thultz.dev.','Cloud sites stay online independently.','Visitors open hub://<site-name>/'} end\\n      if lines then\\n        while true do\\n          local screen=UI.new();screen:header('HOSTING / Quick guide')\\n          local row=5\\n          for _,line in ipairs(lines) do for _,part in ipairs(require('lantern.page').wrap(line,screen.w-2)) do screen:text(2,row,part);row=row+1 end end\\n          screen:footer('Enter / Q / click: back');screen:flush()\\n          local event,key=os.pullEvent()\\n          if event=='mouse_click' or event=='key' and (key==keys.enter or key==keys.q or key==keys.escape) then break end\\n        end\\n      end\\n    else require('lantern.editor').run(sites[pick-2]) end\\n  end\\nend\\nfunction M.run(name,headless)\\n  local S=require('lantern.sites'); local handler=S.handler(name)\\n  local N=require('lantern.network').new(require('lantern.security').identity('site-'..name))\\n  local listener=N.protocol:listen(); local sessions,seen={},{}\\n  local stats={requests=0,errors=0,connections=0}; local tick=os.startTimer(1); local budget=4\\n  local function draw()\\n    if headless then return end\\n    local ui=require('lantern.ui'); local screen=ui.new(); screen:header('HOST / '..name)\\n    screen:text(2,5,'ONLINE  /  ENCRYPTED','accent'); screen:text(2,7,'Requests: '..stats.requests..'   Errors: '..stats.errors)\\n    screen:text(2,9,'Connections: '..stats.connections..' / '..C.maxConnections)\\n    screen:text(2,11,'Identity (share through a trusted channel):','muted')\\n    local row=12; for _,s in ipairs(require('lantern.page').wrap(N.identity.address,screen.w-2)) do screen:text(2,row,s); row=row+1 end\\n    screen:text(2,row+2,'Site storage: '..S.root(name),'muted'); screen:footer('Q Exit  |  Refreshes automatically'); screen:flush()\\n  end\\n  local function loop()\\n    draw()\\n    while true do\\n      local e={os.pullEvent()}; N.observe(e,name)\\n      if e[1]=='timer' and e[2]==tick then\\n        local now=U.now(); budget=4; stats.connections=0\\n        for id,s in pairs(sessions) do if now-s.time>C.timeout then N.close(s.connection); sessions[id]=nil else stats.connections=stats.connections+1 end end\\n        for id,time in pairs(seen) do if now-time>120 then seen[id]=nil end end\\n        tick=os.startTimer(1); draw()\\n      elseif e[1]=='ecnet2_request' and e[2]==listener.id then\\n        local count=0; for _ in pairs(sessions) do count=count+1 end\\n        if count<C.maxConnections and budget>0 then\\n          budget=budget-1\\n          local ok,conn=pcall(listener.accept,listener,{v=1,kind='hello',maxDocument=C.maxDocument},e[3])\\n          if ok then sessions[conn.id]={connection=conn,time=U.now()} else stats.errors=stats.errors+1 end\\n        end\\n      elseif e[1]=='ecnet2_message' and sessions[e[2]] then\\n        local session=sessions[e[2]]; local request=e[4]; local valid=require('lantern.network').validRequest(request)\\n        local replayKey=valid and e[3]..':'..request.id\\n        local entries=0; for _ in pairs(seen) do entries=entries+1 end\\n        if valid and not seen[replayKey] and entries<512 then\\n          seen[replayKey]=U.now(); stats.requests=stats.requests+1\\n          local ok,reply=pcall(handler,request)\\n          if not ok then stats.errors=stats.errors+1; reply={kind='error',status=500,message='Site handler failed'} end\\n          local serialized=textutils.serializeJSON(reply); local total=math.ceil(#serialized/C.chunk)\\n          local sent=pcall(function() for i=1,total do session.connection:send({v=1,kind='chunk',id=request.id,index=i,total=total,data=serialized:sub((i-1)*C.chunk+1,i*C.chunk)}) end end)\\n          if not sent then stats.errors=stats.errors+1 end\\n        else stats.errors=stats.errors+1 end\\n        N.close(session.connection); sessions[e[2]]=nil; draw()\\n      elseif e[1]=='term_resize' then draw()\\n      elseif e[1]=='key' and e[2]==keys.q and not headless then return end\\n    end\\n  end\\n  parallel.waitForAny(require('ecnet2').daemon,loop)\\nend\\nreturn M\\n\",\"size\":5356,\"sha256\":\"e8d961c165b79ee7854b2857413f2ac99e5b10744d59722abed5d5d890f20d47\"},\"lantern/main.lua\":{\"content\":\"local C=require('lantern.config')\\nlocal U=require('lantern.util')\\nlocal UI=require('lantern.ui')\\nreturn function(...)\\n  local args={...}; local command=args[1]\\n  if not command and U.load(C.root..'/current/release.json',{}).profile==2 then command='host' end\\n  fs.makeDir(C.data)\\n  return UI.restore(function()\\n    if command=='host' then\\n      local name=args[2]\\n      if name then require('lantern.host').run(name,args[3]=='--headless')\\n      else require('lantern.host').dashboard() end\\n    elseif command=='edit' then require('lantern.editor').run(args[2])\\n    elseif command=='seed' then require('lantern.security').import(assert(args[2],'Usage: lantern seed <file>')); print('Unique seed imported. Source file removed.'); sleep(1)\\n    elseif command=='trust' then\\n      local alias=assert(args[3],'Usage: lantern trust reset <alias>')\\n      assert(args[2]=='reset','Only trust reset is supported here; verify fingerprints in the browser')\\n      if UI.prompt('Type the alias to remove its identity pin')==alias then local pins=U.load(C.data..'/pins.json',{}); pins[alias]=nil; U.save(C.data..'/pins.json',pins) end\\n    elseif command=='hub' then require('lantern.cloud').configure(assert(args[2],'Usage: lantern hub https://your-hub.example')); print('Hub configured')\\n    elseif command=='publish' then require('lantern.cloud').upload(assert(args[2],'Usage: lantern publish <site>')); print('Press Enter'); read()\\n    elseif command=='doctor' then\\n      print('Lantern '..C.version..' / target CC:Tweaked '..C.target)\\n      print('CraftOS: '..os.version()); print('Host: '..tostring(_HOST)); print('Lua: '.._VERSION)\\n      local w,h=term.getSize(); print(('Terminal: %dx%d / %s'):format(w,h,term.isColor() and 'color' or 'mono'))\\n      print('Free bytes: '..tostring(fs.getFreeSpace('/'))); print('Modems: '..#({peripheral.find('modem')}))\\n      print('Entropy state: '..(fs.exists(C.data..'/private/random') and 'present' or 'missing'))\\n      print('HTTP: '..tostring(http~=nil)); print('Network security: experimental until release acceptance')\\n      print('Press Enter'); read()\\n    elseif command=='update' and not args[2] then\\n      local result=require('lantern.updater').check(print)\\n      UI.message('Lantern updates',result.message)\\n    elseif command=='update' or command=='repair' then\\n      local bundle=args[2] or '/install-lantern.lua'\\n      assert(fs.exists(bundle),'Copy the trusted offline installer from lantern.thultz.dev or supply its local path.')\\n      shell.run(bundle)\\n    elseif command=='uninstall' then\\n      if UI.choose('Uninstall Lantern',{'Cancel','Remove program; keep all user data'})==2 then\\n        if fs.exists('/startup/lantern.lua') then local s=U.read('/startup/lantern.lua'); if s and s:find('-- Lantern managed startup',1,true) then fs.delete('/startup/lantern.lua') end end\\n        fs.delete(C.root); fs.delete('/lantern.lua')\\n      end\\n    else\\n      local action=require('lantern.browser').run(command)\\n      if action=='host' then require('lantern.host').dashboard()\\n      elseif action=='update' then\\n        local result=require('lantern.updater').check(print)\\n        UI.message('Lantern updates',result.message)\\n        return shell.run('/lantern.lua','--skip-update','home')\\n      end\\n    end\\n  end)\\nend\\n\",\"size\":3260,\"sha256\":\"b1b727c8918c586ca19cd0f9e449a42df08a3873697530bdbccc22dc3e0c4cb9\"},\"lantern/network.lua\":{\"content\":\"local C=require('lantern.config')\\nlocal U=require('lantern.util')\\nlocal S=require('lantern.security')\\nlocal M={}\\nfunction M.new(identity)\\n  local E=require('ecnet2')\\n  local N={modems={},discovered={}}\\n  for _,name in ipairs(peripheral.getNames()) do\\n    if peripheral.getType(name)=='modem' then E.open(name); peripheral.call(name,'open',C.discovery); N.modems[#N.modems+1]=name end\\n  end\\n  assert(#N.modems>0,'Attach a wired or wireless modem')\\n  N.protocol=identity:Protocol({name=C.protocol,\\n    serialize=function(v) local s=textutils.serializeJSON(v); assert(#s<=24000,'Message too large'); return s end,\\n    deserialize=function(s) assert(#s<=24000,'Message too large'); return assert(U.json(s)) end})\\n  N.identity=identity\\n  function N.broadcast(message)\\n    local s=textutils.serializeJSON(message)\\n    for _,name in ipairs(N.modems) do if peripheral.isPresent(name) then peripheral.call(name,'transmit',C.discovery,C.discovery,s) end end\\n  end\\n  function N.discover()\\n    for name,site in pairs(N.discovered) do if U.now()-site.time>120 then N.discovered[name]=nil end end\\n    N.broadcast({v=1,kind='discover'}); return N.discovered\\n  end\\n  function N.observe(e,site)\\n    if e[1]~='modem_message' or e[3]~=C.discovery or type(e[5])~='string' or #e[5]>1024 then return end\\n    local msg=U.json(e[5]); if not msg or msg.v~=1 then return end\\n    if msg.kind=='discover' and site and (not N.lastAdvert or U.now()-N.lastAdvert>1) then\\n      N.lastAdvert=U.now(); N.broadcast({v=1,kind='site',alias=site,address=identity.address})\\n    elseif msg.kind=='site' and U.slug(msg.alias) and U.address(msg.address) then\\n      local count=0; for _ in pairs(N.discovered) do count=count+1 end\\n      if count<512 or N.discovered[msg.alias] then\\n        local old=N.discovered[msg.alias]\\n        N.discovered[msg.alias]={address=msg.address,time=U.now(),conflict=old and (old.conflict or old.address~=msg.address)}\\n      end\\n    end\\n  end\\n  -- Pinned upstream has no public close API. Drop this session's registered handler.\\n  function N.close(connection)\\n    if connection and connection._state and connection._state.d then require('ecnet2.ecnetd').removeHandler(connection._state.d) end\\n  end\\n  function N.get(alias,path,method,fields,cancel)\\n    local address\\n    if U.address(alias) then address=alias\\n    else\\n      local pin=S.pins()[alias]\\n      local candidate=N.discovered[alias]\\n      if candidate and candidate.conflict and not pin then error('Conflicting site aliases; use a cryptographic address') end\\n      address=pin and pin.address or (candidate and candidate.address)\\n      if not address then\\n        N.discover(); local deadline=U.now()+2; local discoveryTimer=os.startTimer(2)\\n        repeat local e={os.pullEvent()}; N.observe(e); candidate=N.discovered[alias]\\n          if candidate then assert(not candidate.conflict,'Conflicting alias'); address=candidate.address end\\n        until address or U.now()>deadline or (e[1]=='timer' and e[2]==discoveryTimer)\\n        os.cancelTimer(discoveryTimer)\\n      end\\n    end\\n    assert(address,'Site not discovered. Refresh discovery or use its full fingerprint.')\\n    assert(S.check(alias,address))\\n    local connection=N.protocol:connect(address,N.modems[1])\\n    local ok,result=pcall(function()\\n      local function receive(timeout)\\n        local timer=os.startTimer(timeout)\\n        while true do\\n          local e={os.pullEvent()}\\n          if e[1]=='timer' and e[2]==timer then return nil end\\n          if cancel and cancel() then os.cancelTimer(timer); error('Cancelled') end\\n          if e[1]=='ecnet2_message' and e[2]==connection.id then os.cancelTimer(timer); assert(e[3]==address,'Peer identity mismatch'); return e[4] end\\n        end\\n      end\\n      local hello=receive(C.timeout); assert(hello and hello.kind=='hello' and hello.v==1,'Secure handshake timed out')\\n      assert(S.pin(alias,address,false))\\n      local id=U.hex(require('ccryptolib.random').random(16))\\n      connection:send({v=1,kind='request',id=id,path=assert(U.path(path)),method=method or 'GET',fields=fields or {}})\\n      local parts,total,bytes={},nil,0; local deadline=U.now()+C.timeout\\n      while U.now()<deadline do\\n        local msg=receive(math.max(.01,deadline-U.now())); assert(msg,'Request timed out')\\n        assert(msg.v==1 and msg.id==id and msg.kind=='chunk','Unexpected response')\\n        assert(type(msg.total)=='number' and msg.total%1==0 and msg.total>=1 and msg.total<=16,'Invalid chunk count')\\n        assert(type(msg.index)=='number' and msg.index%1==0 and msg.index>=1 and msg.index<=msg.total,'Invalid chunk index')\\n        assert(type(msg.data)=='string' and #msg.data<=C.chunk,'Invalid chunk')\\n        assert(not total or total==msg.total,'Mixed response'); total=msg.total\\n        assert(not parts[msg.index],'Duplicate chunk'); parts[msg.index]=msg.data; bytes=bytes+#msg.data\\n        assert(bytes<=C.maxDocument,'Response too large')\\n        local complete=true; for i=1,total do if not parts[i] then complete=false end end\\n        if complete then return assert(U.json(table.concat(parts))) end\\n      end\\n      error('Request timed out')\\n    end)\\n    N.close(connection); if not ok then error(result,0) end; return result,address\\n  end\\n  return N\\nend\\nfunction M.validRequest(r)\\n  if type(r)~='table' or r.v~=1 or r.kind~='request' or type(r.id)~='string' or #r.id~=32 or r.id:find('[^%x]') then return false end\\n  if not U.path(r.path) or (r.method~='GET' and r.method~='POST') or type(r.fields)~='table' then return false end\\n  local n=0\\n  for k,v in pairs(r.fields) do n=n+1; if n>32 or type(k)~='string' or #k>40 or (type(v)~='string' and type(v)~='boolean') or (type(v)=='string' and #v>2048) then return false end end\\n  return true\\nend\\nreturn M\\n\",\"size\":5716,\"sha256\":\"5226c6eafd27add562c8a088c8cf9f84728a228b2845d83f699218ecb724e6c5\"},\"lantern/page.lua\":{\"content\":\"local C=require('lantern.config')\\nlocal U=require('lantern.util')\\nlocal M={}\\nlocal kinds={heading=true,paragraph=true,list=true,link=true,image=true,section=true,columns=true,form=true,input=true,checkbox=true,select=true,submit=true}\\nfunction M.validate(page)\\n  local count,seen=0,{}\\n  local function str(v,n) return type(v)=='string' and #v<=(n or 4096) end\\n  local function nodes(list,depth,inForm)\\n    assert(type(list)=='table' and depth<=C.maxDepth,'Invalid page nesting')\\n    assert(not seen[list],'Cyclic page'); seen[list]=true\\n    for k in pairs(list) do assert(type(k)=='number' and k>=1 and k%1==0 and k<=#list,'Expected a node array') end\\n    for _,n in ipairs(list) do\\n      count=count+1; assert(count<=C.maxNodes,'Too many components')\\n      assert(type(n)=='table' and kinds[n.type],'Unknown component')\\n      if n.password~=nil then assert(n.type=='input' and type(n.password)=='boolean','Invalid password field') end\\n      if n.text then assert(str(n.text),'Text too long') end\\n      if n.type=='link' then assert(str(n.href,240) and (n.href:match('^ln://') or n.href:match('^hub://') or U.path(n.href)),'Invalid link') end\\n      if n.type=='image' then\\n        assert(type(n.rows)=='table' and #n.rows<=64,'Invalid image')\\n        for _,r in ipairs(n.rows) do assert(str(r,128) and not r:find('[^0-9a-f ]'),'Image must contain palette digits') end\\n      end\\n      if n.type=='list' then assert(type(n.items)=='table' and #n.items<=128,'Invalid list'); for _,v in ipairs(n.items) do assert(str(v),'Invalid list item') end end\\n      if n.type=='form' then assert(not inForm and U.path(n.action),'Invalid or nested form') end\\n      if n.type=='input' or n.type=='checkbox' or n.type=='select' then\\n        assert(inForm and str(n.name,40) and n.name:match('^[%w_%-]+$'),'Invalid form field')\\n        if n.type=='select' then assert(type(n.options)=='table' and #n.options>0 and #n.options<=32,'Invalid options'); for _,v in ipairs(n.options) do assert(str(v,120),'Invalid option') end end\\n      end\\n      if n.type=='submit' then assert(inForm,'Submit outside form') end\\n      if n.children then nodes(n.children,depth+1,inForm or n.type=='form') end\\n    end\\n  end\\n  local ok,err=pcall(function()\\n    assert(type(page)=='table' and page.version==1 and str(page.title,120),'Invalid page header')\\n    nodes(page.children,1,false)\\n    assert(#textutils.serializeJSON(page)<=C.maxDocument,'Page too large')\\n  end)\\n  return ok,ok and nil or tostring(err)\\nend\\nfunction M.document(title,children) return {version=1,title=title,children=children} end\\nfor name in pairs(kinds) do\\n  M[name]=function(props) props=props or {}; props.type=name; return props end\\nend\\nfunction M.wrap(text,width)\\n  width=math.max(1,width); text=U.clean(text); local out={}\\n  while #text>width do\\n    local cut=width; local s=text:sub(1,width+1):match('^.*() ')\\n    if s and s>1 then cut=s-1 end\\n    out[#out+1]=text:sub(1,cut); text=text:sub(cut+1):gsub('^ +','')\\n  end\\n  out[#out+1]=text; return out\\nend\\n-- Layout returns terminal rows and semantic actions; remote content is never evaluated.\\nfunction M.layout(page,width,values,parentForm)\\n  local rows,actions={},{}; values=values or {}\\n  local function line(text,style,action,pixels)\\n    if action then actions[#actions+1]=action; action.index=#actions; action.row=#rows+1 end\\n    for _,s in ipairs(M.wrap(text,width)) do rows[#rows+1]={text=s,style=style,action=action,pixels=pixels}; pixels=nil end\\n  end\\n  local function render(nodes,form)\\n    for _,n in ipairs(nodes) do\\n      if n.type=='heading' then line(n.text or '', 'heading'); line('','normal')\\n      elseif n.type=='paragraph' then line(n.text or '','normal'); line('','normal')\\n      elseif n.type=='link' then line('> '..(n.text or n.href),'link',{kind='link',href=n.href})\\n      elseif n.type=='list' then for _,v in ipairs(n.items) do line('* '..v,'normal') end\\n      elseif n.type=='image' then for _,r in ipairs(n.rows) do line(string.rep(' ',math.min(#r,width)),'image',nil,r:sub(1,width)) end\\n      elseif n.type=='form' then render(n.children or {},n)\\n      elseif n.type=='input' or n.type=='checkbox' or n.type=='select' then\\n        local key=(form.action or '')..':'..n.name\\n        local value=values[key]; if value==nil then value=n.type=='checkbox' and false or (n.type=='select' and n.options[1] or '') end\\n        values[key]=value\\n        local display=n.type=='checkbox' and (value and '[x]' or '[ ]') or '['..(n.password and string.rep('*',math.min(#tostring(value),24)) or tostring(value))..']'\\n        line((n.text or n.name)..' '..display,'field',{kind=n.type,node=n,key=key,form=form})\\n      elseif n.type=='submit' then line('[ '..(n.text or 'Submit')..' ]','link',{kind='submit',form=form})\\n      elseif n.type=='columns' and width>=44 and #(n.children or {})<=math.floor(width/16) then\\n        -- Each child is one column; columns stack automatically on smaller devices.\\n        local children=n.children or {}; local cw=math.floor((width-(#children-1)*2)/math.max(1,#children))\\n        local cols,max={},0; local base=#rows\\n        for i,child in ipairs(children) do\\n          local cr,ca=M.layout(M.document('',{child}),cw,values,form); cols[i]=cr; max=math.max(max,#cr)\\n          for _,a in ipairs(ca) do actions[#actions+1]=a; a.index=#actions; a.row=base+a.row; a.x=(i-1)*(cw+2)+1; a.width=cw end\\n        end\\n        for y=1,max do local spans={}; for i,cr in ipairs(cols) do spans[#spans+1]={x=(i-1)*(cw+2)+1,row=cr[y],width=cw} end; rows[#rows+1]={spans=spans} end\\n      else render(n.children or {},form) end\\n    end\\n  end\\n  render(page.children,parentForm); return rows,actions\\nend\\nreturn M\\n\",\"size\":5630,\"sha256\":\"564ed3ef9100a93fc770c28d56155f335ad504295be0c9502713294f9c52ccd3\"},\"lantern/release.lua\":{\"content\":\"local U=require('lantern.util')\\nlocal M={}\\nfunction M.verify(envelope,key)\\n  assert(type(envelope)=='table' and type(envelope.payload)=='string' and #envelope.payload<1500000,'Invalid release envelope')\\n  assert(type(envelope.signature)=='string' and #envelope.signature==128,'Invalid signature encoding')\\n  assert(require('ccryptolib.ed25519').verify(U.unhex(key),envelope.payload,U.unhex(envelope.signature)),'Release signature rejected')\\n  local release=assert(textutils.unserializeJSON(envelope.payload))\\n  assert(release.format==1 and type(release.version)=='string' and type(release.files)=='table','Invalid release')\\n  local count,total=0,0\\n  for path,file in pairs(release.files) do\\n    count=count+1; assert(count<=256,'Too many release files')\\n    assert(type(path)=='string' and path:sub(1,1)~='/' and U.path(path) and not path:find('//',1,true),'Unsafe release path')\\n    assert(type(file)=='table' and type(file.content)=='string' and #file.content<=262144,'Invalid release file')\\n    assert(file.size==#file.content,'File size mismatch')\\n    assert(U.hex(require('ccryptolib.sha256').digest(file.content))==file.sha256,'File digest mismatch: '..path)\\n    total=total+#file.content; assert(total<=1200000,'Release too large')\\n  end\\n  assert(release.files['lantern/main.lua'] and release.files['launcher.lua'],'Incomplete release')\\n  return release,total\\nend\\nreturn M\\n\",\"size\":1380,\"sha256\":\"04104f8d8b3c04de57e5802d534e87b99e4d87ad9f05b4b260ed22980f4c8c46\"},\"lantern/security.lua\":{\"content\":\"local U=require('lantern.util')\\nlocal C=require('lantern.config')\\nlocal M={}\\nlocal ready=false\\nfunction M.init()\\n  if ready then return end\\n  local path=C.data..'/private/random'\\n  assert(not fs.exists(path..'.pending'),'Interrupted random-state update. Import a NEW desktop seed.')\\n  local raw=assert(U.read(path,64),'Secure networking needs: lantern seed <unique-seed-file>')\\n  assert(#raw==64,'Corrupt random state. Import a NEW desktop seed.')\\n  local hash=require('ccryptolib.blake3').digest\\n  assert(hash(raw:sub(1,32))==raw:sub(33),'Corrupt random state')\\n  local seed=raw:sub(1,32)\\n  local nextState=hash('Lantern next state v1'..seed)\\n  U.write(path..'.pending',nextState..hash(nextState))\\n  fs.delete(path); fs.move(path..'.pending',path)\\n  require('ccryptolib.random').init(hash('Lantern session v1'..seed))\\n  ready=true\\nend\\nfunction M.import(path)\\n  assert(not ready,'Restart before importing entropy')\\n  local s=assert(U.read(path,128)):gsub('%s','')\\n  assert(#s==64 and not s:find('[^%x]'),'Expected 64 hex characters from desktop secure RNG')\\n  local raw=U.unhex(s); local hash=require('ccryptolib.blake3').digest\\n  local target=C.data..'/private/random'\\n  U.write(target..'.pending',raw..hash(raw))\\n  if fs.exists(target) then fs.delete(target) end\\n  fs.move(target..'.pending',target)\\n  fs.delete(path)\\nend\\nfunction M.identity(name)\\n  M.init(); assert(U.slug(name),'Invalid identity name')\\n  local path=C.data..'/private/'..name\\n  assert(not fs.exists(path..'/id.bin.del'),'Interrupted identity creation; inspect private state')\\n  if fs.exists(path) then assert(fs.exists(path..'/id.bin'),'Missing identity key; refusing silent rotation') end\\n  return require('ecnet2').Identity(path)\\nend\\nfunction M.pins()\\n  local path=C.data..'/pins.json'\\n  if not fs.exists(path) and not fs.exists(path..'.bak') then return {} end\\n  local pins=U.load(path,nil)\\n  assert(type(pins)=='table','Damaged identity pins; restore a trusted backup before networking')\\n  for alias,pin in pairs(pins) do\\n    assert(type(alias)=='string' and type(pin)=='table' and U.address(pin.address) and type(pin.verified)=='boolean','Damaged identity pins; restore a trusted backup before networking')\\n  end\\n  return pins\\nend\\nfunction M.pin(alias,address,verified)\\n  assert(U.address(address),'Invalid identity')\\n  local pins=M.pins()\\n  local previous=pins[alias]\\n  if previous and previous.address~=address then return nil,'IDENTITY CHANGED: remove the old pin explicitly with lantern trust reset '..alias end\\n  pins[alias]={address=address,verified=verified or (previous and previous.verified) or false}\\n  U.save(C.data..'/pins.json',pins); return pins[alias]\\nend\\nfunction M.check(alias,address)\\n  local pin=M.pins()[alias]\\n  if pin and pin.address~=address then return nil,'Site identity changed; connection blocked' end\\n  return true,pin and pin.verified or false\\nend\\nreturn M\\n\",\"size\":2861,\"sha256\":\"5e54335bcd5411b258480974f489288f7c17ac5743f06cfbb12a13c87e52282c\"},\"lantern/sites.lua\":{\"content\":\"local C=require('lantern.config')\\nlocal U=require('lantern.util')\\nlocal P=require('lantern.page')\\nlocal M={}\\nfunction M.root(name) assert(U.slug(name),'Use a lowercase site name with letters, numbers and hyphens'); return C.data..'/sites/'..name end\\nfunction M.list()\\n  local root=C.data..'/sites'; if not fs.exists(root) then return {} end\\n  local out={}; for _,name in ipairs(fs.list(root)) do if U.slug(name) and fs.isDir(root..'/'..name) then out[#out+1]=name end end; return out\\nend\\nfunction M.create(name,template)\\n  local root=M.root(name); assert(not fs.exists(root),'Site already exists')\\n  local templateRoot=C.root..'/current/templates/'..template\\n  assert(fs.exists(templateRoot),'Unknown template'); fs.copy(templateRoot,root)\\n  return root\\nend\\nfunction M.handler(name)\\n  local root=M.root(name); local fn\\n  if fs.exists(root..'/app.lua') then fn=assert(loadfile(root..'/app.lua',nil,_ENV))(); assert(type(fn)=='function','app.lua must return function(ctx). print() writes to the host console, not the page. Return a page using ctx.page instead.') end\\n  return function(request)\\n    local path=assert(U.path(request.path)); local reply\\n    if fn then\\n      reply=fn({method=request.method,path=path,fields=request.fields,requestId=request.id,\\n        page=P,read=function(key,default) assert(U.slug(key)); return U.load(root..'/data/'..key..'.json',default) end,\\n        write=function(key,value) assert(request.method=='POST','GET cannot change storage'); assert(U.slug(key)); U.save(root..'/data/'..key..'.json',value) end})\\n    end\\n    if not reply then\\n      if request.method~='GET' then return {kind='error',status=405,message='Method not allowed'} end\\n      if path=='/' then path='/index.json' end\\n      if not path:match('%.json$') or path:match('^/data/') then return {kind='error',status=404,message='Page not found'} end\\n      local s=U.read(root..path,C.maxDocument); if not s then return {kind='error',status=404,message='Page not found'} end\\n      reply={kind='page',page=assert(U.json(s))}\\n    end\\n    assert(type(reply)=='table','Invalid handler response')\\n    if reply.kind=='page' then assert(P.validate(reply.page))\\n    elseif reply.kind=='redirect' then assert(U.path(reply.path),'Redirect must remain on this site')\\n    elseif reply.kind=='resource' then assert(type(reply.data)=='string' and #reply.data<=65536,'Invalid resource')\\n    elseif reply.kind=='error' then reply.message=U.clean(reply.message):sub(1,256)\\n    else error('Unknown response kind') end\\n    assert(#textutils.serializeJSON(reply)<=C.maxDocument,'Response exceeds transfer limit')\\n    return reply\\n  end\\nend\\nreturn M\\n\",\"size\":2624,\"sha256\":\"dee6be710937b202422a3ea4bab9144c707047a9e502d95fe515aae7168f4af5\"},\"lantern/ui.lua\":{\"content\":\"local U=require('lantern.util')\\nlocal M={}\\nlocal palette={bg='f',panel='7',text='0',muted='8',accent='1',link='3',field='4',heading='1',normal='0'}\\nfunction M.new()\\n  local w,h=term.getSize(); local color=term.isColor(); local self={w=w,h=h,lines={},previous={}}\\n  local function c(name,bg) if not color then return bg and 'f' or '0' end; return palette[name] or name or (bg and 'f' or '0') end\\n  for y=1,h do self.lines[y]={string.rep(' ',w),string.rep(c('text'),w),string.rep(c('bg',true),w)} end\\n  function self:text(x,y,s,fg,bg)\\n    if y<1 or y>h then return end\\n    s=U.clean(s); if x<1 then s=s:sub(2-x); x=1 end; s=s:sub(1,w-x+1); if #s==0 then return end\\n    local row=self.lines[y]; local vals={s,string.rep(c(fg),#s),string.rep(c(bg,true),#s)}\\n    for i=1,3 do row[i]=row[i]:sub(1,x-1)..vals[i]..row[i]:sub(x+#s) end\\n  end\\n  function self:bar(y,bg) self:text(1,y,string.rep(' ',w),'text',bg) end\\n  function self:header(title)\\n    self:bar(1,'accent'); self:text(2,1,'/\\\\\\\\  LANTERN','bg','accent')\\n    self:text(2,3,title,'heading')\\n  end\\n  function self:footer(text) self:bar(h,'panel'); self:text(2,h,text,'text','panel') end\\n  function self:pixels(x,y,pixels)\\n    for i=1,#pixels do local v=pixels:sub(i,i); if v~=' ' then self:text(x+i-1,y,color and ' ' or '#','text',color and v or 'bg') end end\\n  end\\n  function self:flush()\\n    local last=M.last or {}\\n    for y,row in ipairs(self.lines) do\\n      if not last[y] or table.concat(row)~=table.concat(last[y]) then term.setCursorPos(1,y); term.blit(row[1],row[2],row[3]) end\\n    end\\n    M.last=self.lines; term.setCursorBlink(false)\\n  end\\n  return self\\nend\\nfunction M.message(title,message)\\n  local offset=0\\n  M.last=nil\\n  while true do\\n    local screen=M.new();screen:header(title)\\n    local rows={}\\n    for line in (tostring(message)..'\\\\n'):gmatch('(.-)\\\\n') do\\n      for _,part in ipairs(require('lantern.page').wrap(line,screen.w-2)) do rows[#rows+1]=part end\\n    end\\n    local visible=math.max(1,screen.h-5);offset=math.max(0,math.min(offset,#rows-visible))\\n    for i=1,visible do if rows[offset+i] then screen:text(2,3+i,rows[offset+i]) end end\\n    screen:footer('Up/Down scroll | Enter/Q back');screen:flush()\\n    local e,k=os.pullEvent()\\n    if e=='key' then\\n      if k==keys.enter or k==keys.q or k==keys.escape then M.last=nil;return end\\n      if k==keys.up then offset=offset-1 elseif k==keys.down then offset=offset+1 end\\n    elseif e=='mouse_scroll' then offset=offset+k\\n    elseif e=='mouse_click' then M.last=nil;return end\\n  end\\nend\\nfunction M.prompt(label,default,mask)\\n  M.last=nil;local screen=M.new();screen:header(label)\\n  screen:text(2,5,'Type below, then press Enter.','muted')\\n  screen:bar(7,'panel');screen:footer('Enter: confirm');screen:flush()\\n  term.setCursorPos(2,7);term.setBackgroundColor(colors.gray);term.setTextColor(colors.white)\\n  local s=read(mask,nil,nil,default or '');M.last=nil;return s\\nend\\nfunction M.choose(title,options)\\n  local selected=1\\n  while true do\\n    local screen=M.new(); screen:header(title)\\n    local start=math.max(1,selected-math.max(1,screen.h-7)+1)\\n    for i=start,math.min(#options,start+screen.h-7) do screen:text(2,5+i-start,(i==selected and '> ' or '  ')..options[i],i==selected and 'accent' or 'text') end\\n    screen:footer('Click to choose / Enter select / Q back'); screen:flush()\\n    local e,k,x,y=os.pullEvent()\\n    if e=='mouse_click' and y>=5 and y<screen.h then\\n      local choice=start+y-5;if options[choice] then M.last=nil;return choice end\\n    elseif e=='mouse_scroll' then selected=math.max(1,math.min(#options,selected+k))\\n    elseif e=='key' then\\n      if k==keys.up then selected=math.max(1,selected-1) elseif k==keys.down then selected=math.min(#options,selected+1)\\n      elseif k==keys.enter then return selected elseif k==keys.q or k==keys.escape then return nil end\\n    end\\n  end\\nend\\nfunction M.restore(fn)\\n  local fg,bg=term.getTextColor(),term.getBackgroundColor(); local x,y=term.getCursorPos(); local blink=term.getCursorBlink()\\n  local ok,err=pcall(fn)\\n  term.setBackgroundColor(bg); term.setTextColor(fg); term.clear(); term.setCursorPos(x,y); term.setCursorBlink(blink); M.last=nil\\n  if not ok and tostring(err)~='Terminated' then printError(err) end\\n  return ok,err\\nend\\nreturn M\\n\",\"size\":4232,\"sha256\":\"54a9496c2001d3bee6f0de897f91a8a05eaadd73c914124a061dcb7fada26335\"},\"lantern/updater.lua\":{\"content\":\"-- Only signed data is downloaded. The installed verification key and source are trusted.\\nlocal C=require('lantern.config')\\nlocal U=require('lantern.util')\\nlocal M={}\\nlocal source='https://lantern.thultz.dev/downloads/'\\nlocal function digest(s) return U.hex(require('ccryptolib.sha256').digest(s)) end\\nlocal function fetch(name,limit)\\n  assert(http,'HTTP is disabled')\\n  local h,err,failed=http.get({url=source..name,redirect=false,binary=true,timeout=5})\\n  if not h then if failed then failed.close() end;error(err or 'Update service unavailable') end\\n  local ok,value=pcall(function()\\n    assert(h.getResponseCode()==200,'Update service unavailable')\\n    local parts,total={},0\\n    while true do\\n      local part=h.read(8192);if not part then break end\\n      total=total+#part;assert(total<=limit,'Update response too large');parts[#parts+1]=part\\n    end\\n    return table.concat(parts)\\n  end)\\n  h.close();assert(ok,value);return value\\nend\\nfunction M.recover()\\n  local root=C.root\\n  if fs.exists(root..'/transaction') then\\n    if fs.exists(root..'/previous') then\\n      if fs.exists(root..'/current') then fs.delete(root..'/current') end\\n      fs.move(root..'/previous',root..'/current')\\n    end\\n    fs.delete(root..'/transaction')\\n  end\\nend\\nfunction M.check(progress)\\n  progress=progress or function() end\\n  local ok,result=pcall(function()\\n    M.recover()\\n    progress('Lantern: checking for updates...')\\n    local installed=U.load(C.root..'/current/release.json',{})\\n    assert(installed.profile==1 or installed.profile==2 or installed.profile==3,'Installation metadata needs repair')\\n    local key=assert(U.read(C.root..'/current/update-key.txt',128),'Update verification key missing; run the latest trusted installer once')\\n    local index=assert(textutils.unserializeJSON(fetch('latest.json',4096)))\\n    assert(type(index)=='table' and type(index.payload)=='string' and #index.payload<2048 and type(index.signature)=='string' and #index.signature==128,'Invalid update announcement')\\n    assert(require('ccryptolib.ed25519').verify(U.unhex(key),index.payload,U.unhex(index.signature)),'Update signature rejected')\\n    local info=assert(textutils.unserializeJSON(index.payload))\\n    assert(info.format==1 and type(info.sequence)=='number' and info.sequence%1==0 and info.sequence>0 and type(info.version)=='string' and #info.version<40,'Invalid update metadata')\\n    assert(type(info.size)=='number' and info.size>0 and info.size<=1500000 and type(info.sha256)=='string' and #info.sha256==64,'Invalid update size or digest')\\n    local current=math.max(tonumber(installed.sequence) or 0,C.sequence or 0)\\n    if info.sequence<=current then return {updated=false,message='Lantern is up to date ('..tostring(installed.version or C.version)..').'} end\\n    progress('Downloading Lantern '..info.version..'...')\\n    local raw=fetch('release.json',info.size)\\n    assert(#raw==info.size and digest(raw)==info.sha256,'Update download failed integrity checks')\\n    local release,total=require('lantern.release').verify(assert(textutils.unserializeJSON(raw)),key)\\n    assert(release.sequence==info.sequence and release.version==info.version,'Release does not match announcement')\\n    assert(release.files['update-key.txt'] and release.files['update-key.txt'].content==key,'Unexpected update verification key change')\\n    local free=fs.getFreeSpace(C.root)\\n    assert(type(free)~='number' or free>total+65536,'Not enough free disk space to stage update')\\n    local stage=C.root..'/stage'\\n    if fs.exists(stage) then fs.delete(stage) end\\n    local paths={};for path in pairs(release.files) do paths[#paths+1]=path end;table.sort(paths)\\n    for i,path in ipairs(paths) do\\n      local content=release.files[path].content;U.write(stage..'/'..path,content)\\n      assert(U.read(stage..'/'..path,262144)==content,'Staged file verification failed')\\n      if i%8==0 or i==#paths then progress('Updating Lantern: '..i..' / '..#paths..' files');sleep(0) end\\n    end\\n    U.save(stage..'/release.json',{version=release.version,sequence=release.sequence,profile=installed.profile})\\n    -- User data and startup entries are outside this transaction. The stable launcher\\n    -- remains in place so it can restore /previous if power is lost during activation.\\n    if fs.exists(C.root..'/previous') then fs.delete(C.root..'/previous') end\\n    U.write(C.root..'/transaction','pending')\\n    fs.move(C.root..'/current',C.root..'/previous')\\n    fs.move(stage,C.root..'/current')\\n    fs.delete(C.root..'/transaction')\\n    return {updated=true,message='Updated Lantern to '..release.version..'.',version=release.version}\\n  end)\\n  if ok then progress(result.message);return result end\\n  local recovered=pcall(M.recover)\\n  local message='Update unavailable: '..U.clean(result)..'. Continuing with installed Lantern.'\\n  if not recovered then error('Update recovery needs the offline repair installer: '..tostring(result)) end\\n  progress(message);return {updated=false,error=tostring(result),message=message}\\nend\\nreturn M\\n\",\"size\":4987,\"sha256\":\"93f4e23cd13a9b0bc6e54af98d9786b40a3eeab125e20c10e2f57c17f455d04e\"},\"lantern/util.lua\":{\"content\":\"local M = {}\\nfunction M.hex(s) return (s:gsub('.', function(c) return ('%02x'):format(c:byte()) end)) end\\nfunction M.unhex(s)\\n  assert(type(s)=='string' and #s%2==0 and not s:find('[^%x]'), 'Invalid hex')\\n  return (s:gsub('..', function(c) return string.char(tonumber(c,16)) end))\\nend\\nfunction M.path(s)\\n  if type(s)~='string' or #s>240 or s:find('[%z\\\\1-\\\\31\\\\\\\\:%%?#]') then return nil,'Invalid path' end\\n  for part in s:gmatch('[^/]+') do if part=='.' or part=='..' then return nil,'Unsafe path' end end\\n  return '/'..s:gsub('^/+',''):gsub('/+','/')\\nend\\nfunction M.slug(s) return type(s)=='string' and #s>0 and #s<=40 and s:match('^[a-z0-9][a-z0-9%-]*$')~=nil end\\nfunction M.address(s) return type(s)=='string' and #s==44 and s:match('^[A-Za-z0-9_%-]+=$')~=nil end\\nfunction M.now() return os.epoch('utc')/1000 end\\nfunction M.read(path,limit)\\n  if not fs.exists(path) or fs.isDir(path) then return nil,'File not found: '..path end\\n  if fs.getSize(path)>(limit or 262144) then return nil,'File too large' end\\n  local f,e=fs.open(path,'rb'); if not f then return nil,e end\\n  local s=f.readAll(); f.close(); return s\\nend\\nfunction M.write(path,s)\\n  fs.makeDir(fs.getDir(path)); local f=assert(fs.open(path,'wb')); f.write(s); f.close()\\nend\\n-- Recover only user-data transactions. Security state deliberately never rolls back.\\nfunction M.atomic(path,s)\\n  local tmp,bak=path..'.new',path..'.bak'\\n  if fs.exists(tmp) then fs.delete(tmp) end\\n  M.write(tmp,s)\\n  if fs.exists(bak) then fs.delete(bak) end\\n  if fs.exists(path) then fs.move(path,bak) end\\n  fs.move(tmp,path)\\n  if fs.exists(bak) then fs.delete(bak) end\\nend\\nfunction M.json(s)\\n  if type(s)~='string' or #s>262144 then return nil,'Document too large' end\\n  local ok,v=pcall(textutils.unserializeJSON,s)\\n  if not ok or type(v)~='table' then return nil,'Invalid JSON' end\\n  return v\\nend\\nfunction M.load(path,default)\\n  if not fs.exists(path) and fs.exists(path..'.bak') then fs.move(path..'.bak',path) end\\n  local s=M.read(path); local v=s and M.json(s)\\n  return v or default\\nend\\nfunction M.save(path,v) M.atomic(path,textutils.serializeJSON(v)) end\\nfunction M.clean(s) return tostring(s or ''):gsub('[%z\\\\1-\\\\31\\\\127]',' ') end\\nreturn M\\n\",\"size\":2183,\"sha256\":\"9ed5019db8da3e2ba44c57afcc5ab4827318cd6503a21069608d0001bf1f13dd\"},\"launcher.lua\":{\"content\":\"local root='/lantern'\\nif fs.exists(root..'/transaction') then\\n  if fs.exists(root..'/previous') then\\n    if fs.exists(root..'/current') then fs.delete(root..'/current') end\\n    fs.move(root..'/previous',root..'/current')\\n  end\\n  fs.delete(root..'/transaction')\\nend\\nassert(fs.exists(root..'/current/lantern/main.lua'),'Installation incomplete. Run the offline installer to repair.')\\npackage.path=root..'/current/?.lua;'..root..'/current/?/init.lua;'..package.path\\nlocal args={...}\\nlocal skip=args[1]=='--skip-update'\\nif skip then table.remove(args,1) end\\nlocal command=args[1]\\nlocal maintenance={seed=true,trust=true,doctor=true,hub=true,publish=true,update=true,repair=true,uninstall=true}\\nif not skip and not maintenance[command or ''] then\\n  local result=require('lantern.updater').check(function(message) print(message) end)\\n  if result.updated then\\n    -- Start a fresh shell environment so no modules from the old release remain loaded.\\n    return shell.run('/lantern.lua','--skip-update',table.unpack(args))\\n  end\\nend\\nreturn require('lantern.main')(table.unpack(args))\\n\",\"size\":1075,\"sha256\":\"6380fe7f00e0ae824effbc594a7c8014ee2eeb996df78dc979b52cc0aea3055d\"},\"ccryptolib/aead.lua\":{\"content\":\"--- The ChaCha20Poly1305AEAD authenticated encryption with associated data (AEAD) construction.\\n\\nlocal expect   = require \\\"cc.expect\\\".expect\\nlocal lassert = require \\\"ccryptolib.internal.util\\\".lassert\\nlocal packing  = require \\\"ccryptolib.internal.packing\\\"\\nlocal chacha20 = require \\\"ccryptolib.chacha20\\\"\\nlocal poly1305 = require \\\"ccryptolib.poly1305\\\"\\n\\nlocal p8x1, fmt8x1 = packing.compilePack(\\\"<I8\\\")\\nlocal u4x4, fmt4x4 = packing.compileUnpack(\\\"<I4I4I4I4\\\")\\nlocal bxor = bit32.bxor\\n\\n--- Encrypts a message.\\n--- @param key string A 32-byte random key.\\n--- @param nonce string A 12-byte per-message unique nonce.\\n--- @param message string The message to be encrypted.\\n--- @param aad string aad Arbitrary associated data to also authenticate.\\n--- @param rounds number? The number of ChaCha20 rounds to use. Defaults to 20.\\n--- @return string ctx The ciphertext.\\n--- @return string tag The 16-byte authentication tag.\\nlocal function encrypt(key, nonce, message, aad, rounds)\\n    expect(1, key, \\\"string\\\")\\n    lassert(#key == 32, \\\"key length must be 32\\\", 2)\\n    expect(2, nonce, \\\"string\\\")\\n    lassert(#nonce == 12, \\\"nonce length must be 12\\\", 2)\\n    expect(3, message, \\\"string\\\")\\n    expect(4, aad, \\\"string\\\")\\n    rounds = expect(5, rounds, \\\"number\\\", \\\"nil\\\") or 20\\n    lassert(rounds % 2 == 0, \\\"round number must be even\\\", 2)\\n    lassert(rounds >= 8, \\\"round number must be no smaller than 8\\\", 2)\\n    lassert(rounds <= 20, \\\"round number must be no larger than 20\\\", 2)\\n\\n    -- Generate auth key and encrypt.\\n    local msgLong = (\\\"\\\\0\\\"):rep(64) .. message\\n    local ctxLong = chacha20.crypt(key, nonce, msgLong, rounds, 0)\\n    local authKey = ctxLong:sub(1, 32)\\n    local ciphertext = ctxLong:sub(65)\\n\\n    -- Authenticate.\\n    local pad1 = (\\\"\\\\0\\\"):rep(-#aad % 16)\\n    local pad2 = (\\\"\\\\0\\\"):rep(-#ciphertext % 16)\\n    local aadLen = p8x1(\\\"<I8\\\", #aad)\\n    local ctxLen = p8x1(\\\"<I8\\\", #ciphertext)\\n    local combined = aad .. pad1 .. ciphertext .. pad2 .. aadLen .. ctxLen\\n    local tag = poly1305.mac(authKey, combined)\\n\\n    return ciphertext, tag\\nend\\n\\n--- Decrypts a message.\\n--- @param key string The key used on encryption.\\n--- @param nonce string The nonce used on encryption.\\n--- @param tag string The authentication tag returned on encryption.\\n--- @param ciphertext string The ciphertext to be decrypted.\\n--- @param aad string The arbitrary associated data used on encryption.\\n--- @param rounds number The number of rounds used on encryption.\\n--- @return string? msg The decrypted plaintext. Or nil on auth failure.\\nlocal function decrypt(key, nonce, tag, ciphertext, aad, rounds)\\n    expect(1, key, \\\"string\\\")\\n    lassert(#key == 32, \\\"key length must be 32\\\", 2)\\n    expect(2, nonce, \\\"string\\\")\\n    lassert(#nonce == 12, \\\"nonce length must be 12\\\", 2)\\n    expect(3, tag, \\\"string\\\")\\n    lassert(#tag == 16, \\\"tag length must be 16\\\", 2)\\n    expect(4, ciphertext, \\\"string\\\")\\n    expect(5, aad, \\\"string\\\")\\n    rounds = expect(6, rounds, \\\"number\\\", \\\"nil\\\") or 20\\n    lassert(rounds % 2 == 0, \\\"round number must be even\\\", 2)\\n    lassert(rounds >= 8, \\\"round number must be no smaller than 8\\\", 2)\\n    lassert(rounds <= 20, \\\"round number must be no larger than 20\\\", 2)\\n\\n    -- Generate auth key.\\n    local authKey = chacha20.crypt(key, nonce, (\\\"\\\\0\\\"):rep(32), rounds, 0)\\n\\n    -- Check tag.\\n    local pad1 = (\\\"\\\\0\\\"):rep(-#aad % 16)\\n    local pad2 = (\\\"\\\\0\\\"):rep(-#ciphertext % 16)\\n    local aadLen = p8x1(fmt8x1, #aad)\\n    local ctxLen = p8x1(fmt8x1, #ciphertext)\\n    local combined = aad .. pad1 .. ciphertext .. pad2 .. aadLen .. ctxLen\\n    local t1, t2, t3, t4 = u4x4(fmt4x4, tag, 1)\\n    local u1, u2, u3, u4 = u4x4(fmt4x4, poly1305.mac(authKey, combined), 1)\\n    local eq = bxor(t1, u1) + bxor(t2, u2) + bxor(t3, u3) + bxor(t4, u4)\\n    if eq ~= 0 then return nil end\\n\\n    -- Decrypt\\n    return chacha20.crypt(key, nonce, ciphertext, rounds)\\nend\\n\\nreturn {\\n    encrypt = encrypt,\\n    decrypt = decrypt,\\n}\\n\",\"size\":3866,\"sha256\":\"738c69c55a2894ffde108267dbdc4ae2d953d6b3122dbc03002389fe65fb67a0\"},\"ccryptolib/blake3.lua\":{\"content\":\"--- The BLAKE3 cryptographic hash function.\\n\\nlocal expect = require \\\"cc.expect\\\".expect\\nlocal lassert = require \\\"ccryptolib.internal.util\\\".lassert\\nlocal packing = require \\\"ccryptolib.internal.packing\\\"\\n\\nlocal unpack = unpack or table.unpack\\nlocal bxor = bit32.bxor\\nlocal rol = bit32.lrotate\\nlocal p16x4, fmt16x4 = packing.compilePack(\\\"<I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4\\\")\\nlocal u16x4 = packing.compileUnpack(fmt16x4)\\nlocal u8x4, fmt8x4 = packing.compileUnpack(\\\"<I4I4I4I4I4I4I4I4\\\")\\n\\nlocal CHUNK_START = 0x01\\nlocal CHUNK_END = 0x02\\nlocal PARENT = 0x04\\nlocal ROOT = 0x08\\nlocal KEYED_HASH = 0x10\\nlocal DERIVE_KEY_CONTEXT = 0x20\\nlocal DERIVE_KEY_MATERIAL = 0x40\\n\\nlocal IV = {\\n    0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a,\\n    0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19,\\n}\\n\\nlocal function compress(h, msg, t, v14, v15, full)\\n    local h00, h01, h02, h03, h04, h05, h06, h07 = unpack(h)\\n    local v00, v01, v02, v03 = h00, h01, h02, h03\\n    local v04, v05, v06, v07 = h04, h05, h06, h07\\n    local v08, v09, v10, v11 = 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a\\n    local v12 = t % 2 ^ 32\\n    local v13 = (t - v12) * 2 ^ -32\\n\\n    local m00, m01, m02, m03, m04, m05, m06, m07,\\n          m08, m09, m10, m11, m12, m13, m14, m15 = unpack(msg)\\n\\n    local tmp\\n    for i = 1, 7 do\\n        v00 = v00 + v04 + m00 v12 = rol(bxor(v12, v00), 16)\\n        v08 = v08 + v12       v04 = rol(bxor(v04, v08), 20)\\n        v00 = v00 + v04 + m01 v12 = rol(bxor(v12, v00), 24)\\n        v08 = v08 + v12       v04 = rol(bxor(v04, v08), 25)\\n\\n        v01 = v01 + v05 + m02 v13 = rol(bxor(v13, v01), 16)\\n        v09 = v09 + v13       v05 = rol(bxor(v05, v09), 20)\\n        v01 = v01 + v05 + m03 v13 = rol(bxor(v13, v01), 24)\\n        v09 = v09 + v13       v05 = rol(bxor(v05, v09), 25)\\n\\n        v02 = v02 + v06 + m04 v14 = rol(bxor(v14, v02), 16)\\n        v10 = v10 + v14       v06 = rol(bxor(v06, v10), 20)\\n        v02 = v02 + v06 + m05 v14 = rol(bxor(v14, v02), 24)\\n        v10 = v10 + v14       v06 = rol(bxor(v06, v10), 25)\\n\\n        v03 = v03 + v07 + m06 v15 = rol(bxor(v15, v03), 16)\\n        v11 = v11 + v15       v07 = rol(bxor(v07, v11), 20)\\n        v03 = v03 + v07 + m07 v15 = rol(bxor(v15, v03), 24)\\n        v11 = v11 + v15       v07 = rol(bxor(v07, v11), 25)\\n\\n        v00 = v00 + v05 + m08 v15 = rol(bxor(v15, v00), 16)\\n        v10 = v10 + v15       v05 = rol(bxor(v05, v10), 20)\\n        v00 = v00 + v05 + m09 v15 = rol(bxor(v15, v00), 24)\\n        v10 = v10 + v15       v05 = rol(bxor(v05, v10), 25)\\n\\n        v01 = v01 + v06 + m10 v12 = rol(bxor(v12, v01), 16)\\n        v11 = v11 + v12       v06 = rol(bxor(v06, v11), 20)\\n        v01 = v01 + v06 + m11 v12 = rol(bxor(v12, v01), 24)\\n        v11 = v11 + v12       v06 = rol(bxor(v06, v11), 25)\\n\\n        v02 = v02 + v07 + m12 v13 = rol(bxor(v13, v02), 16)\\n        v08 = v08 + v13       v07 = rol(bxor(v07, v08), 20)\\n        v02 = v02 + v07 + m13 v13 = rol(bxor(v13, v02), 24)\\n        v08 = v08 + v13       v07 = rol(bxor(v07, v08), 25)\\n\\n        v03 = v03 + v04 + m14 v14 = rol(bxor(v14, v03), 16)\\n        v09 = v09 + v14       v04 = rol(bxor(v04, v09), 20)\\n        v03 = v03 + v04 + m15 v14 = rol(bxor(v14, v03), 24)\\n        v09 = v09 + v14       v04 = rol(bxor(v04, v09), 25)\\n\\n        if i ~= 7 then\\n            tmp = m02\\n            m02 = m03\\n            m03 = m10\\n            m10 = m12\\n            m12 = m09\\n            m09 = m11\\n            m11 = m05\\n            m05 = m00\\n            m00 = tmp\\n\\n            tmp = m06\\n            m06 = m04\\n            m04 = m07\\n            m07 = m13\\n            m13 = m14\\n            m14 = m15\\n            m15 = m08\\n            m08 = m01\\n            m01 = tmp\\n        end\\n    end\\n\\n    if full then\\n        return {\\n            bxor(v00, v08), bxor(v01, v09), bxor(v02, v10), bxor(v03, v11),\\n            bxor(v04, v12), bxor(v05, v13), bxor(v06, v14), bxor(v07, v15),\\n            bxor(v08, h00), bxor(v09, h01), bxor(v10, h02), bxor(v11, h03),\\n            bxor(v12, h04), bxor(v13, h05), bxor(v14, h06), bxor(v15, h07),\\n        }\\n    else\\n        return {\\n            bxor(v00, v08), bxor(v01, v09), bxor(v02, v10), bxor(v03, v11),\\n            bxor(v04, v12), bxor(v05, v13), bxor(v06, v14), bxor(v07, v15),\\n        }\\n    end\\nend\\n\\nlocal function merge(cvl, cvr)\\n    for i = 1, 8 do cvl[i + 8] = cvr[i] end\\n    return cvl\\nend\\n\\nlocal function blake3(iv, flags, msg, len)\\n    -- Set up the state.\\n    local stateCvs = {}\\n    local stateCv = iv\\n    local stateT = 0\\n    local stateN = 0\\n    local stateStart = CHUNK_START\\n    local stateEnd = 0\\n\\n    -- Digest complete blocks.\\n    for i = 1, #msg - 64, 64 do\\n        -- Compress the block.\\n        local block = {u16x4(fmt16x4, msg, i)}\\n        local stateFlags = flags + stateStart + stateEnd\\n        stateCv = compress(stateCv, block, stateT, 64, stateFlags)\\n        stateStart = 0\\n        stateN = stateN + 1\\n\\n        if stateN == 15 then\\n            -- Last block in chunk.\\n            stateEnd = CHUNK_END\\n        elseif stateN == 16 then\\n            -- Chunk complete, merge.\\n            local mergeCv = stateCv\\n            local mergeAmt = stateT + 1\\n            while mergeAmt % 2 == 0 do\\n                local block = merge(table.remove(stateCvs), mergeCv)\\n                mergeCv = compress(iv, block, 0, 64, flags + PARENT)\\n                mergeAmt = mergeAmt / 2\\n            end\\n\\n            -- Push back.\\n            table.insert(stateCvs, mergeCv)\\n\\n            -- Update state back to next chunk.\\n            stateCv = iv\\n            stateT = stateT + 1\\n            stateN = 0\\n            stateStart = CHUNK_START\\n            stateEnd = 0\\n        end\\n    end\\n\\n    -- Pad the last message block.\\n    local lastLen = #msg == 0 and 0 or (#msg - 1) % 64 + 1\\n    local padded = msg:sub(-lastLen) .. (\\\"\\\\0\\\"):rep(64)\\n    local last = {u16x4(fmt16x4, padded, 1)}\\n\\n    -- Prepare output expansion state.\\n    local outCv, outBlock, outLen, outFlags\\n    if stateT > 0 then\\n        -- Root is a parent, digest last block now and merge parents.\\n        local stateFlags = flags + stateStart + CHUNK_END\\n        local mergeCv = compress(stateCv, last, stateT, lastLen, stateFlags)\\n        for i = #stateCvs, 2, -1 do\\n            local block = merge(stateCvs[i], mergeCv)\\n            mergeCv = compress(iv, block, 0, 64, flags + PARENT)\\n        end\\n\\n        -- Set output state.\\n        outCv = iv\\n        outBlock = merge(stateCvs[1], mergeCv)\\n        outLen = 64\\n        outFlags = flags + ROOT + PARENT\\n    else\\n        -- Root block is in the first chunk, set output state.\\n        outCv = stateCv\\n        outBlock = last\\n        outLen = lastLen\\n        outFlags = flags + stateStart + CHUNK_END + ROOT\\n    end\\n\\n    -- Expand output.\\n    local out = {}\\n    for i = 0, len / 64 do\\n        local md = compress(outCv, outBlock, i, outLen, outFlags, true)\\n        out[i + 1] = p16x4(fmt16x4, unpack(md))\\n    end\\n\\n    return table.concat(out):sub(1, len)\\nend\\n\\n--- Hashes data using BLAKE3.\\n--- @param message string The input message.\\n--- @param len number? The desired hash length, in bytes. Defaults to 32.\\n--- @return string hash The hash.\\nlocal function digest(message, len)\\n    expect(1, message, \\\"string\\\")\\n    len = expect(2, len, \\\"number\\\", \\\"nil\\\") or 32\\n    lassert(len % 1 == 0, \\\"desired output length must be an integer\\\", 2)\\n    lassert(len >= 1, \\\"desired output length must be positive\\\", 2)\\n    return blake3(IV, 0, message, len)\\nend\\n\\n--- Performs a keyed hash.\\n--- @param key string A 32-byte random key.\\n--- @param message string The input message.\\n--- @param len number? The desired hash length, in bytes. Defaults to 32.\\n--- @return string hash The keyed hash.\\nlocal function digestKeyed(key, message, len)\\n    expect(1, key, \\\"string\\\")\\n    lassert(#key == 32, \\\"key length must be 32\\\", 2)\\n    expect(2, message, \\\"string\\\")\\n    len = expect(3, len, \\\"number\\\", \\\"nil\\\") or 32\\n    lassert(len % 1 == 0, \\\"desired output length must be an integer\\\", 2)\\n    lassert(len >= 1, \\\"desired output length must be positive\\\", 2)\\n    return blake3({u8x4(fmt8x4, key, 1)}, KEYED_HASH, message, len)\\nend\\n\\n--- Makes a context-based key derivation function (KDF).\\n--- @param context string The context for the KDF.\\n--- @return fun(material: string, len: number?): string kdf The KDF.\\nlocal function deriveKey(context)\\n    expect(1, context, \\\"string\\\")\\n    local iv = {u8x4(fmt8x4, blake3(IV, DERIVE_KEY_CONTEXT, context, 32), 1)}\\n\\n    --- Derives a key.\\n    --- @param material string The keying material.\\n    --- @param len number? The desired hash length, in bytes. Defaults to 32.\\n    return function(material, len)\\n        expect(1, material, \\\"string\\\")\\n        len = expect(2, len, \\\"number\\\", \\\"nil\\\") or 32\\n        lassert(len % 1 == 0, \\\"desired output length must be an integer\\\", 2)\\n        lassert(len >= 1, \\\"desired output length must be positive\\\", 2)\\n        return blake3(iv, DERIVE_KEY_MATERIAL, material, len)\\n    end\\nend\\n\\nreturn {\\n    digest = digest,\\n    digestKeyed = digestKeyed,\\n    deriveKey = deriveKey,\\n}\\n\",\"size\":8917,\"sha256\":\"a495f9ab1c7ffaeeb6a273788adce9ba2fbe2012a5ba46bd8bce0624698982d1\"},\"ccryptolib/chacha20.lua\":{\"content\":\"--- The ChaCha20 stream cipher.\\n\\nlocal expect  = require \\\"cc.expect\\\".expect\\nlocal lassert = require \\\"ccryptolib.internal.util\\\".lassert\\nlocal packing = require \\\"ccryptolib.internal.packing\\\"\\n\\nlocal bxor = bit32.bxor\\nlocal rol = bit32.lrotate\\nlocal u8x4, fmt8x4 = packing.compileUnpack(\\\"<I4I4I4I4I4I4I4I4\\\")\\nlocal u3x4, fmt3x4 = packing.compileUnpack(\\\"<I4I4I4\\\")\\nlocal p16x4, fmt16x4 = packing.compilePack(\\\"<I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4\\\")\\nlocal u16x4 = packing.compileUnpack(fmt16x4)\\n\\n--- Encrypts/Decrypts data using ChaCha20.\\n--- @param key string A 32-byte random key.\\n--- @param nonce string A 12-byte per-message unique nonce.\\n--- @param message string A plaintext or ciphertext.\\n--- @param rounds number? The number of ChaCha20 rounds to use. Defaults to 20.\\n--- @param offset number? The block offset to generate the keystream at. Defaults to 1.\\n--- @return string out The resulting ciphertext or plaintext.\\nlocal function crypt(key, nonce, message, rounds, offset)\\n    expect(1, key, \\\"string\\\")\\n    lassert(#key == 32, \\\"key length must be 32\\\", 2)\\n    expect(2, nonce, \\\"string\\\")\\n    lassert(#nonce == 12, \\\"nonce length must be 12\\\", 2)\\n    expect(3, message, \\\"string\\\")\\n    rounds = expect(4, rounds, \\\"number\\\", \\\"nil\\\") or 20\\n    lassert(rounds % 2 == 0, \\\"round number must be even\\\", 2)\\n    lassert(rounds >= 8, \\\"round number must be no smaller than 8\\\", 2)\\n    lassert(rounds <= 20, \\\"round number must be no larger than 20\\\", 2)\\n    offset = expect(5, offset, \\\"number\\\", \\\"nil\\\") or 1\\n    lassert(offset % 1 == 0, \\\"offset must be an integer\\\", 2)\\n    lassert(offset >= 0, \\\"offset must be nonnegative\\\", 2)\\n    lassert(#message + 64 * offset <= 2 ^ 38, \\\"offset too large\\\", 2)\\n\\n    -- Build the state block.\\n    local i0, i1, i2, i3 = 0x61707865, 0x3320646e, 0x79622d32, 0x6b206574\\n    local k0, k1, k2, k3, k4, k5, k6, k7 = u8x4(fmt8x4, key, 1)\\n    local cr, n0, n1, n2 = offset, u3x4(fmt3x4, nonce, 1)\\n\\n    -- Pad the message.\\n    local padded = message .. (\\\"\\\\0\\\"):rep(-#message % 64)\\n\\n    -- Expand and combine.\\n    local out = {}\\n    local idx = 1\\n    for i = 1, #padded / 64 do\\n        -- Copy the block.\\n        local s00, s01, s02, s03 = i0, i1, i2, i3\\n        local s04, s05, s06, s07 = k0, k1, k2, k3\\n        local s08, s09, s10, s11 = k4, k5, k6, k7\\n        local s12, s13, s14, s15 = cr, n0, n1, n2\\n\\n        -- Iterate.\\n        for _ = 1, rounds, 2 do\\n            s00 = s00 + s04 s12 = rol(bxor(s12, s00), 16)\\n            s08 = s08 + s12 s04 = rol(bxor(s04, s08), 12)\\n            s00 = s00 + s04 s12 = rol(bxor(s12, s00), 8)\\n            s08 = s08 + s12 s04 = rol(bxor(s04, s08), 7)\\n\\n            s01 = s01 + s05 s13 = rol(bxor(s13, s01), 16)\\n            s09 = s09 + s13 s05 = rol(bxor(s05, s09), 12)\\n            s01 = s01 + s05 s13 = rol(bxor(s13, s01), 8)\\n            s09 = s09 + s13 s05 = rol(bxor(s05, s09), 7)\\n\\n            s02 = s02 + s06 s14 = rol(bxor(s14, s02), 16)\\n            s10 = s10 + s14 s06 = rol(bxor(s06, s10), 12)\\n            s02 = s02 + s06 s14 = rol(bxor(s14, s02), 8)\\n            s10 = s10 + s14 s06 = rol(bxor(s06, s10), 7)\\n\\n            s03 = s03 + s07 s15 = rol(bxor(s15, s03), 16)\\n            s11 = s11 + s15 s07 = rol(bxor(s07, s11), 12)\\n            s03 = s03 + s07 s15 = rol(bxor(s15, s03), 8)\\n            s11 = s11 + s15 s07 = rol(bxor(s07, s11), 7)\\n\\n            s00 = s00 + s05 s15 = rol(bxor(s15, s00), 16)\\n            s10 = s10 + s15 s05 = rol(bxor(s05, s10), 12)\\n            s00 = s00 + s05 s15 = rol(bxor(s15, s00), 8)\\n            s10 = s10 + s15 s05 = rol(bxor(s05, s10), 7)\\n\\n            s01 = s01 + s06 s12 = rol(bxor(s12, s01), 16)\\n            s11 = s11 + s12 s06 = rol(bxor(s06, s11), 12)\\n            s01 = s01 + s06 s12 = rol(bxor(s12, s01), 8)\\n            s11 = s11 + s12 s06 = rol(bxor(s06, s11), 7)\\n\\n            s02 = s02 + s07 s13 = rol(bxor(s13, s02), 16)\\n            s08 = s08 + s13 s07 = rol(bxor(s07, s08), 12)\\n            s02 = s02 + s07 s13 = rol(bxor(s13, s02), 8)\\n            s08 = s08 + s13 s07 = rol(bxor(s07, s08), 7)\\n\\n            s03 = s03 + s04 s14 = rol(bxor(s14, s03), 16)\\n            s09 = s09 + s14 s04 = rol(bxor(s04, s09), 12)\\n            s03 = s03 + s04 s14 = rol(bxor(s14, s03), 8)\\n            s09 = s09 + s14 s04 = rol(bxor(s04, s09), 7)\\n        end\\n\\n        -- Decode message block.\\n        local m00, m01, m02, m03, m04, m05, m06, m07\\n        local m08, m09, m10, m11, m12, m13, m14, m15\\n\\n        m00, m01, m02, m03, m04, m05, m06, m07,\\n        m08, m09, m10, m11, m12, m13, m14, m15, idx =\\n            u16x4(fmt16x4, padded, idx)\\n\\n        -- Feed-forward and combine.\\n        out[i] = p16x4(fmt16x4,\\n            bxor(m00, s00 + i0), bxor(m01, s01 + i1),\\n            bxor(m02, s02 + i2), bxor(m03, s03 + i3),\\n            bxor(m04, s04 + k0), bxor(m05, s05 + k1),\\n            bxor(m06, s06 + k2), bxor(m07, s07 + k3),\\n            bxor(m08, s08 + k4), bxor(m09, s09 + k5),\\n            bxor(m10, s10 + k6), bxor(m11, s11 + k7),\\n            bxor(m12, s12 + cr), bxor(m13, s13 + n0),\\n            bxor(m14, s14 + n1), bxor(m15, s15 + n2)\\n        )\\n\\n        -- Increment counter.\\n        cr = cr + 1\\n    end\\n\\n    return table.concat(out):sub(1, #message)\\nend\\n\\nreturn {\\n    crypt = crypt,\\n}\\n\",\"size\":5149,\"sha256\":\"a741161fba6bcf04556554aa42b7b304e4b639f01b53601d462907df39cd6dac\"},\"ccryptolib/config.lua\":{\"content\":\"local expect = require \\\"cc.expect\\\".expect\\n\\nlocal usePeripheralsValue = true\\n\\n--- Sets whether to use peripheral calls for computation. Defaults to true.\\n---\\n--- If this is true, ccryptolib will search for compatible peripherals every few\\n--- seconds. If a compatible peripheral is found, ccryptolib will offload some\\n--- of its computation to it, improving performance.\\n---\\n--- No peripheral searches are performed until you first call a ccryptolib\\n--- function. Setting this value to false beforehand guarantees no searches will\\n--- ever be made.\\n---\\n--- @param value boolean? The new setting. If nil then no changes are made.\\n--- @return boolean value The current setting. Includes any changes made.\\nlocal function usePeripherals(value)\\n    expect(1, value, \\\"boolean\\\", \\\"nil\\\")\\n    if value ~= nil then usePeripheralsValue = value end\\n    return usePeripheralsValue\\nend\\n\\nreturn {\\n    usePeripherals = usePeripherals,\\n}\\n\",\"size\":919,\"sha256\":\"bc95f607825536e21f6bc8d936990cbd1c997fe6641ee031aaef972a11371287\"},\"ccryptolib/ed25519.lua\":{\"content\":\"--- The Ed25519 digital signature scheme.\\n\\nlocal expect = require \\\"cc.expect\\\".expect\\nlocal lassert = require \\\"ccryptolib.internal.util\\\".lassert\\nlocal hw = require \\\"ccryptolib.internal.hw\\\"\\nlocal fq = require \\\"ccryptolib.internal.fq\\\"\\nlocal sha512 = require \\\"ccryptolib.internal.sha512\\\"\\nlocal ed = require \\\"ccryptolib.internal.edwards25519\\\"\\nlocal random = require \\\"ccryptolib.random\\\"\\n\\n--- Computes a public key from a secret key.\\n--- @param sk string A random 32-byte secret key.\\n--- @return string pk The matching 32-byte public key.\\nlocal function publicKey(sk)\\n    expect(1, sk, \\\"string\\\")\\n    assert(#sk == 32, \\\"secret key length must be 32\\\")\\n    local ok, out = hw.ed25519PublicKey(sk)\\n    if ok then return out end\\n\\n    local h = sha512.digest(sk)\\n    local x = fq.decodeClamped(h:sub(1, 32))\\n\\n    return ed.encode(ed.mulG(fq.bits(x)))\\nend\\n\\n--- Signs a message.\\n--- @param sk string The signer's secret key.\\n--- @param pk string The signer's public key.\\n--- @param msg string The message to be signed.\\n--- @return string sig The 64-byte signature on the message.\\nlocal function sign(sk, pk, msg)\\n    expect(1, sk, \\\"string\\\")\\n    lassert(#sk == 32, \\\"secret key length must be 32\\\", 2)\\n    expect(2, pk, \\\"string\\\")\\n    lassert(#pk == 32, \\\"public key length must be 32\\\", 2)\\n    expect(3, msg, \\\"string\\\")\\n\\n    -- Note: We use randomization as a side channel attack mitigation attempt.\\n    -- Calls to other libraries will almost surely use the standardized\\n    -- deterministic signatues instead.\\n    local ok, out = hw.ed25519Sign(msg, sk)\\n    if ok then return out end\\n\\n    -- Secret key.\\n    local h = sha512.digest(sk)\\n    local x = fq.decodeClamped(h:sub(1, 32))\\n\\n    -- Commitment.\\n    local k = fq.decodeWide(random.random(64))\\n    local r = ed.mulG(fq.bits(k))\\n    local rStr = ed.encode(r)\\n\\n    -- Challenge.\\n    local e = fq.decodeWide(sha512.digest(rStr .. pk .. msg))\\n\\n    -- Response.\\n    local m = fq.decodeWide(random.random(64))\\n    local s = fq.sub(fq.add(k, fq.mul(fq.add(x, m), e)), fq.mul(m, e))\\n    local sStr = fq.encode(s)\\n\\n    return rStr .. sStr\\nend\\n\\n--- Verifies a signature on a message.\\n--- @param pk string The signer's public key.\\n--- @param msg string The signed message.\\n--- @param sig string The alleged signature.\\n--- @return boolean valid Whether the signature is valid or not.\\nlocal function verify(pk, msg, sig)\\n    expect(1, pk, \\\"string\\\")\\n    lassert(#pk == 32, \\\"public key length must be 32\\\", 2) --- @cast pk String32\\n    expect(2, msg, \\\"string\\\")\\n    expect(3, sig, \\\"string\\\")\\n    lassert(#sig == 64, \\\"signature length must be 64\\\", 2)\\n\\n    -- Note: The library verifier may use strict signature verification, which\\n    -- rejects signatures that we don't with some adversarial signatures. We\\n    -- make no guarantees on those so we can get by regardless.\\n    local ok, out = hw.ed25519Verify(msg, sig, pk)\\n    if ok then return out end\\n\\n    local y = ed.decode(pk)\\n    if not y then return false end\\n\\n    local rStr = sig:sub(1, 32)\\n    local sStr = sig:sub(33)\\n\\n    local e = fq.decodeWide(sha512.digest(rStr .. pk .. msg))\\n\\n    local gs = ed.mulG(fq.bits(fq.decode(sStr)))\\n    local ye = ed.mul(y, fq.bits(e))\\n    local rv = ed.sub(gs, ed.niels(ye))\\n\\n    return ed.encode(rv) == rStr\\nend\\n\\nreturn {\\n    publicKey = publicKey,\\n    sign = sign,\\n    verify = verify,\\n}\\n\",\"size\":3300,\"sha256\":\"15af5a1773347460c59a47d2635e1d6e67ae7f9940b896b8f7740b1f378ea5e0\"},\"ccryptolib/internal/curve25519.lua\":{\"content\":\"--- Point arithmetic on the Curve25519 Montgomery curve.\\n\\nlocal fp = require \\\"ccryptolib.internal.fp\\\"\\nlocal ed = require \\\"ccryptolib.internal.edwards25519\\\"\\nlocal random = require \\\"ccryptolib.random\\\"\\n\\n--- @class MtPoint A point class on Curve25519, in XZ coordinates.\\n--- @field [1] number[] The X coordinate.\\n--- @field [2] number[] The Z coordinate.\\n\\n--- Doubles a point.\\n--- @param P1 MtPoint The point to double.\\n--- @return MtPoint P2 P1 + P1.\\nlocal function double(P1)\\n    local x1, z1 = P1[1], P1[2]\\n    local a = fp.add(x1, z1)\\n    local aa = fp.square(a)\\n    local b = fp.sub(x1, z1)\\n    local bb = fp.square(b)\\n    local c = fp.sub(aa, bb)\\n    local x3 = fp.mul(aa, bb)\\n    local z3 = fp.mul(c, fp.add(bb, fp.kmul(c, 121666)))\\n    return {x3, z3}\\nend\\n\\n--- Computes differential addition on two points.\\n--- @param DP MtPoint P1 - P2.\\n--- @param P1 MtPoint The first point to add.\\n--- @param P2 MtPoint The second point to add.\\n--- @return MtPoint P3 P1 + P2.\\nlocal function dadd(DP, P1, P2)\\n    local dx, dz = DP[1], DP[2]\\n    local x1, z1 = P1[1], P1[2]\\n    local x2, z2 = P2[1], P2[2]\\n    local a = fp.add(x1, z1)\\n    local b = fp.sub(x1, z1)\\n    local c = fp.add(x2, z2)\\n    local d = fp.sub(x2, z2)\\n    local da = fp.mul(d, a)\\n    local cb = fp.mul(c, b)\\n    local x3 = fp.mul(dz, fp.square(fp.add(da, cb)))\\n    local z3 = fp.mul(dx, fp.square(fp.sub(da, cb)))\\n    return {x3, z3}\\nend\\n\\n--- Performs a step on the Montgomery ladder.\\n--- @param DP MtPoint P1 - P2.\\n--- @param P1 MtPoint The first point.\\n--- @param P2 MtPoint The second point.\\n--- @return MtPoint P3 2A\\n--- @return MtPoint P4 A + B\\nlocal function step(DP, P1, P2)\\n    local dx, dz = DP[1], DP[2]\\n    local x1, z1 = P1[1], P1[2]\\n    local x2, z2 = P2[1], P2[2]\\n    local a = fp.add(x1, z1)\\n    local aa = fp.square(a)\\n    local b = fp.sub(x1, z1)\\n    local bb = fp.square(b)\\n    local e = fp.sub(aa, bb)\\n    local c = fp.add(x2, z2)\\n    local d = fp.sub(x2, z2)\\n    local da = fp.mul(d, a)\\n    local cb = fp.mul(c, b)\\n    local x4 = fp.mul(dz, fp.square(fp.add(da, cb)))\\n    local z4 = fp.mul(dx, fp.square(fp.sub(da, cb)))\\n    local x3 = fp.mul(aa, bb)\\n    local z3 = fp.mul(e, fp.add(bb, fp.kmul(e, 121666)))\\n    return {x3, z3}, {x4, z4}\\nend\\n\\nlocal function ladder(DP, bits)\\n    local P = {fp.num(1), fp.num(0)}\\n    local Q = DP\\n\\n    for i = #bits, 1, -1 do\\n        if bits[i] == 0 then\\n            P, Q = step(DP, P, Q)\\n        else\\n            Q, P = step(DP, Q, P)\\n        end\\n    end\\n\\n    return P\\nend\\n\\n--- Performs a scalar multiplication operation with multiplication by 8.\\n--- @param P MtPoint The base point.\\n--- @param bits number[] The scalar multiplier, in little-endian bits.\\n--- @return MtPoint product The product, multiplied by 8.\\nlocal function ladder8(P, bits)\\n    -- Randomize.\\n    local rf = fp.decode(random.random(32) --[[@as String32, length is given]])\\n    P = {fp.mul(P[1], rf), fp.mul(P[2], rf)}\\n\\n    -- Multiply.\\n    return double(double(double(ladder(P, bits))))\\nend\\n\\n--- Scales a point's coordinates.\\n--- @param P MtPoint The input point.\\n--- @return MtPoint Q The same point P, but with Z = 1.\\nlocal function scale(P)\\n    return {fp.mul(P[1], fp.invert(P[2])), fp.num(1)}\\nend\\n\\n--- Encodes a scaled point.\\n--- @param P MtPoint The scaled point to encode.\\n--- @return string encoded P, encoded into a 32-byte string.\\nlocal function encode(P)\\n    return fp.encode(P[1])\\nend\\n\\n--- Decodes a point.\\n--- @param str String32 A 32-byte encoded point.\\n--- @return MtPoint pt The decoded point.\\nlocal function decode(str)\\n    return {fp.decode(str), fp.num(1)}\\nend\\n\\n--- Decodes an Edwards25519 encoded point into Curve25519, ignoring the sign.\\n---\\n--- There is a single exception: The identity point (0, 1), which gets mapped\\n--- into the 2-torsion point (0, 0), which isn't the identity of Curve25519.\\n---\\n--- @param str String32 A 32-byte encoded Edwards25519 point.\\n--- @return MtPoint pt The decoded point, mapped into Curve25519.\\nlocal function decodeEd(str)\\n    local y = fp.decode(str)\\n    local n = fp.carry(fp.add(fp.num(1), y))\\n    local d = fp.carry(fp.sub(fp.num(1), y))\\n    if fp.eqz(d) then\\n        return {fp.num(0), fp.num(1)}\\n    else\\n        return {n, d}\\n    end\\nend\\n\\n--- Performs a scalar multiplication by the base point G.\\n--- @param bits number[] The scalar multiplier, in little-endian bits.\\n--- @return MtPoint product The product point.\\nlocal function mulG(bits)\\n    -- Multiply by G on Edwards25519.\\n    local P = ed.mulG(bits)\\n\\n    -- Use the birational map to get the point on Curve25519.\\n    -- Never fails since G is in the large group, and the exponent is clamped.\\n    local Py, Pz = P[2], P[3]\\n    local Rx = fp.carry(fp.add(Py, Pz))\\n    local Rz = fp.carry(fp.sub(Pz, Py))\\n\\n    return {Rx, Rz}\\nend\\n\\n--- Computes a twofold product from a ruleset.\\n---\\n--- Returns nil if any of the results would be equal to the identity.\\n---\\n--- @param P MtPoint The base point.\\n--- @param ruleset __TYPE_TODO The ruleset generated by scalars m, n.\\n--- @return MtPoint? A [8m]P.\\n--- @return MtPoint? B [8n]P.\\n--- @return MtPoint? C [8m]P - [8n]P.\\nlocal function prac(P, ruleset)\\n    -- Randomize.\\n    local rf = fp.decode(random.random(32) --[[@as String32, length is given]])\\n    local A = {fp.mul(P[1], rf), fp.mul(P[2], rf)}\\n\\n    -- Start the base at [8]P.\\n    local A = double(double(double(A)))\\n\\n    -- Throw away small order points.\\n    if fp.eqz(A[2]) then return end\\n\\n    -- Now e = d = gcd(m, n).\\n    -- Update A from [8]P to [8 * gcd(m, n)]P.\\n    A = ladder(A, ruleset[1])\\n\\n    -- Reject rulesets where m = n.\\n    local rules = ruleset[2]\\n    if #rules == 0 then return end\\n\\n    -- Evaluate the first rule.\\n    -- Since e = d, this means A - B = C = O. Differential addition fails when\\n    -- C = O, so we need to treat this case specially.\\n    -- Note that rules 0 and 1 never happen last, since the algorithm would stop\\n    -- one step earlier if they did:\\n    -- - If after rule 0 we had e = d, then (d, e) \\u2192 (e, d) would also mean that\\n    --   e = d, so it stops one step earlier.\\n    -- - If after rule 1 we had e = d, then (d, e) \\u2192 ((2d - e)/3, (2e - d)/3)\\n    --   would mean that (2d - e)/3 = (2e - d)/3, thus 2d - e = 2e - d, thus\\n    --   3d = 3e, thus d = e, so it stops one step earlier.\\n    local B, C\\n    local rule = rules[#rules]\\n    if rule == 2 then\\n        -- (A, B, C) \\u2190 (2A + B, B, 2A) = (3A, A, 2A)\\n        local A2 = double(A)\\n        A, B, C = dadd(A, A2, A), A, A2\\n    elseif rule == 3 or rule == 5 then\\n        -- (A, B, C) \\u2190 (A + B, B, A) = (2A, A, A)\\n        -- or (A, B, C) \\u2190 (2A, B, 2A - B) = (2A, A, A)\\n        A, B, C = double(A), A, A\\n    elseif rule == 6 then\\n        -- (A, B, C) \\u2190 (3A + 3B, B, 3A + 2B) = (6A, A, 5A)\\n        local A2 = double(A)\\n        local A3 = dadd(A, A2, A)\\n        A, B, C = double(A3), A, dadd(A, A3, A2)\\n    elseif rule == 7 then\\n        -- (A, B, C) \\u2190 (3A + 2B, B, 3A + B) = (5A, A, 4A)\\n        local A2 = double(A)\\n        local A3 = dadd(A, A2, A)\\n        local A4 = double(A2)\\n        A, B, C = dadd(A3, A4, A), A, A4\\n    elseif rule == 8 then\\n        -- (A, B, C) \\u2190 (3A + B, B, 3A) = (4A, A, 3A)\\n        local A2 = double(A)\\n        local A3 = dadd(A, A2, A)\\n        A, B, C = double(A2), A, A3\\n    else\\n        -- (A, B, C) \\u2190 (A, 2B, A - 2B) = (A, 2A, A)\\n        A, B, C = A, double(A), A\\n    end\\n\\n    -- Evaluate the other rules.\\n    -- Let's assume addition is undefined here, this happens when A - B = O.\\n    -- Since A = [d]P and B = [e]P, A = B happens when:\\n    -- (1) P is on the large order base group and d \\u2261 e (mod q).\\n    -- (2) P is on the large order twist group and d \\u2261 e (mod q').\\n    -- (3) P is on a small order group.\\n    -- Case (3) never happens since we throw small order points away above.\\n    -- Since 0 \\u2264 {d, e} < q < q', a modular equivalence here means an integer\\n    -- equivalence. Therefore d = e.\\n    -- However, the ruleset stops when d = e, therefore the algorithm must have\\n    -- stopped earlier than when it did. Contradiction.\\n    -- Therefore, addition is always defined.\\n    -- Furthermore, the PRAC invariants mean that this product is the same as\\n    -- if the points were multiplied separately.\\n    for i = #rules - 1, 1, -1 do\\n        local rule = rules[i]\\n        if rule == 0 then\\n            -- (A, B, C) \\u2190 (B, A, B - A)\\n            A, B = B, A\\n        elseif rule == 1 then\\n            -- (A, B, C) \\u2190 (2A + B, A + 2B, A - B)\\n            local AB = dadd(C, A, B)\\n            A, B = dadd(B, AB, A), dadd(A, AB, B)\\n        elseif rule == 2 then\\n            -- (A, B, C) \\u2190 (2A + B, B, 2A)\\n            A, C = dadd(B, dadd(C, A, B), A), double(A)\\n        elseif rule == 3 then\\n            -- (A, B, C) \\u2190 (A + B, B, A)\\n            A, C = dadd(C, A, B), A\\n        elseif rule == 5 then\\n            -- (A, B, C) \\u2190 (2A, B, 2A - B)\\n            A, C = double(A), dadd(B, A, C)\\n        elseif rule == 6 then\\n            -- (A, B, C) \\u2190 (3A + 3B, B, 3A + 2B)\\n            local AB = dadd(C, A, B)\\n            local AABB = double(AB)\\n            A, C = dadd(AB, AABB, AB), dadd(dadd(A, AB, B), AABB, A)\\n        elseif rule == 7 then\\n            -- (A, B, C) \\u2190 (3A + 2B, B, 3A + B)\\n            local AB = dadd(C, A, B)\\n            local AAB = dadd(B, AB, A)\\n            A, C = dadd(A, AAB, AB), dadd(AB, AAB, A)\\n        elseif rule == 8 then\\n            -- (A, B, C) \\u2190 (3A + B, B, 3A)\\n            local AA = double(A)\\n            A, C = dadd(C, AA, dadd(C, A, B)), dadd(A, AA, A)\\n        else\\n            -- (A, B, C) \\u2190 (A, 2B, A - 2B)\\n            B, C = double(B), dadd(A, C, B)\\n        end\\n    end\\n\\n    return A, B, C\\nend\\n\\nreturn {\\n    G = {fp.num(9), fp.num(1)},\\n    dadd = dadd,\\n    scale = scale,\\n    encode = encode,\\n    decode = decode,\\n    decodeEd = decodeEd,\\n    ladder8 = ladder8,\\n    mulG = mulG,\\n    prac = prac,\\n}\\n\",\"size\":9789,\"sha256\":\"8730d8f2b7970c835e3a2a42816a8879fee1cc318dca85bacaca0b7b873e8cab\"},\"ccryptolib/internal/edwards25519.lua\":{\"content\":\"--- Point arithmetic on the Edwards25519 Edwards curve.\\n\\nlocal fp = require \\\"ccryptolib.internal.fp\\\"\\n\\nlocal unpack = unpack or table.unpack\\n\\n--- @class EdPoint A point on Edwards25519, in extended coordinates.\\n--- @field [1] number[] The X coordinate.\\n--- @field [2] number[] The Y coordinate.\\n--- @field [3] number[] The Z coordinate.\\n--- @field [4] number[] The T coordinate.\\n\\n--- @class NsPoint A point on Edwards25519, in Niels' coordinates.\\n--- @field [1] number[] Preprocessed Y + X.\\n--- @field [2] number[] Preprocessed Y - X.\\n--- @field [3] number[] Preprocessed 2Z.\\n--- @field [4] number[] Preprocessed 2DT.\\n\\nlocal D = fp.mul(fp.num(-121665), fp.invert(fp.num(121666)))\\nlocal K = fp.kmul(D, 2)\\n\\n--- @type EdPoint\\nlocal O = {fp.num(0), fp.num(1), fp.num(1), fp.num(0)}\\nlocal G = nil\\n\\n--- Doubles a point.\\n--- @param P1 EdPoint The point to double.\\n--- @return EdPoint P2 P1 + P1.\\nlocal function double(P1)\\n    -- Unsoundness: fp.sub(g, e), and fp.sub(d, i) break fp.sub's contract since\\n    -- it doesn't accept an fp2. Although not ideal, in practice this doesn't\\n    -- matter since fp.carry handles the larger sum.\\n    local P1x, P1y, P1z = unpack(P1)\\n    local a = fp.square(P1x)\\n    local b = fp.square(P1y)\\n    local c = fp.square(P1z)\\n    local d = fp.add(c, c)\\n    local e = fp.add(a, b)\\n    local f = fp.add(P1x, P1y)\\n    local g = fp.square(f)\\n    local h = fp.carry(fp.sub(g, e))\\n    local i = fp.sub(b, a)\\n    local j = fp.carry(fp.sub(d, i))\\n    local P3x = fp.mul(h, j)\\n    local P3y = fp.mul(i, e)\\n    local P3z = fp.mul(j, i)\\n    local P3t = fp.mul(h, e)\\n    return {P3x, P3y, P3z, P3t}\\nend\\n\\n--- Adds two points.\\n--- @param P1 EdPoint The first summand point.\\n--- @param N2 NsPoint The second summand point.\\n--- @return EdPoint P3 P1 + P2, where N2 = niels(P2).\\nlocal function add(P1, N2)\\n    local P1x, P1y, P1z, P1t = unpack(P1)\\n    local N1p, N1m, N1z, N1t = unpack(N2)\\n    local a = fp.sub(P1y, P1x)\\n    local b = fp.mul(a, N1m)\\n    local c = fp.add(P1y, P1x)\\n    local d = fp.mul(c, N1p)\\n    local e = fp.mul(P1t, N1t)\\n    local f = fp.mul(P1z, N1z)\\n    local g = fp.sub(d, b)\\n    local h = fp.sub(f, e)\\n    local i = fp.add(f, e)\\n    local j = fp.add(d, b)\\n    local P3x = fp.mul(g, h)\\n    local P3y = fp.mul(i, j)\\n    local P3z = fp.mul(h, i)\\n    local P3t = fp.mul(g, j)\\n    return {P3x, P3y, P3z, P3t}\\nend\\n\\n--- Subtracts one point from another.\\n--- @param P1 EdPoint The first summand point.\\n--- @param N2 NsPoint The second summand point.\\n--- @return EdPoint P3 P1 - P2, where N2 = niels(P2).\\nlocal function sub(P1, N2)\\n    local P1x, P1y, P1z, P1t = unpack(P1)\\n    local N1p, N1m, N1z, N1t = unpack(N2)\\n    local a = fp.sub(P1y, P1x)\\n    local b = fp.mul(a, N1p)\\n    local c = fp.add(P1y, P1x)\\n    local d = fp.mul(c, N1m)\\n    local e = fp.mul(P1t, N1t)\\n    local f = fp.mul(P1z, N1z)\\n    local g = fp.sub(d, b)\\n    local h = fp.add(f, e)\\n    local i = fp.sub(f, e)\\n    local j = fp.add(d, b)\\n    local P3x = fp.mul(g, h)\\n    local P3y = fp.mul(i, j)\\n    local P3z = fp.mul(h, i)\\n    local P3t = fp.mul(g, j)\\n    return {P3x, P3y, P3z, P3t}\\nend\\n\\n--- Computes the Niels representation of a point.\\n--- @param P1 EdPoint The input point.\\n--- @return NsPoint N1 Niels' precomputation applied to P1.\\nlocal function niels(P1)\\n    local P1x, P1y, P1z, P1t = unpack(P1)\\n    local N3p = fp.add(P1y, P1x)\\n    local N3m = fp.sub(P1y, P1x)\\n    local N3z = fp.add(P1z, P1z)\\n    local N3t = fp.mul(P1t, K)\\n    return {N3p, N3m, N3z, N3t}\\nend\\n\\n--- Scales a point.\\n--- @param P1 EdPoint The input point.\\n--- @return EdPoint P2 The same point as P1, but with Z = 1.\\nlocal function scale(P1)\\n    local P1x, P1y, P1z = unpack(P1)\\n    local zInv = fp.invert(P1z)\\n    local P3x = fp.mul(P1x, zInv)\\n    local P3y = fp.mul(P1y, zInv)\\n    local P3z = fp.num(1)\\n    local P3t = fp.mul(P3x, P3y)\\n    return {P3x, P3y, P3z, P3t}\\nend\\n\\n--- Encodes a scaled point.\\n--- @param P1 EdPoint The scaled point to encode.\\n--- @return string out P1 encoded as a 32-byte string.\\nlocal function encode(P1)\\n    P1 = scale(P1)\\n    local P1x, P1y = unpack(P1)\\n    local y = fp.encode(P1y)\\n    local xBit = fp.canonicalize(P1x)[1] % 2\\n    return y:sub(1, -2) .. string.char(y:byte(-1) + xBit * 128)\\nend\\n\\n--- Decodes a point.\\n--- @param str String32 A 32-byte encoded point.\\n--- @return EdPoint? P1 The decoded point, or nil if it isn't on the curve.\\nlocal function decode(str)\\n    local P3y = fp.decode(str)\\n    local a = fp.square(P3y)\\n    local b = fp.sub(a, fp.num(1))\\n    local c = fp.mul(a, D)\\n    local d = fp.add(c, fp.num(1))\\n    local P3x = fp.sqrtDiv(b, d)\\n    if not P3x then return nil end\\n    local xBit = fp.canonicalize(P3x)[1] % 2\\n    if xBit ~= bit32.extract(str:byte(-1), 7) then\\n        P3x = fp.carry(fp.neg(P3x))\\n    end\\n    local P3z = fp.num(1)\\n    local P3t = fp.mul(P3x, P3y)\\n    return {P3x, P3y, P3z, P3t}\\nend\\n\\nG = decode(\\\"Xfffffffffffffffffffffffffffffff\\\") --[[@as EdPoint, G is valid]]\\n\\n--- Transforms little-endian bits into a signed radix-2^w form.\\n--- @param bits number[]\\n--- @param w number Log2 of the radix, must be at least 1.\\n--- @return number[]\\nlocal function signedRadixW(bits, w)\\n    -- TODO Find a more elegant way of doing this.\\n    local wPow = 2 ^ w\\n    local wPowh = wPow / 2\\n    local out = {}\\n    local acc = 0\\n    local mul = 1\\n    for i = 1, #bits do\\n        acc = acc + bits[i] * mul\\n        mul = mul * 2\\n        while i == #bits and acc > 0 or mul > wPow do\\n            local rem = acc % wPow\\n            if rem >= wPowh then rem = rem - wPow end\\n            acc = (acc - rem) / wPow\\n            mul = mul / wPow\\n            out[#out + 1] = rem\\n        end\\n    end\\n    return out\\nend\\n\\n--- Computes a multiplication table for radix-2^w form multiplication.\\n--- @param P EdPoint The base point.\\n--- @param w number Log2 of the radix, must be at least 1.\\n--- @return NsPoint[][]\\nlocal function radixWTable(P, w)\\n    local out = {}\\n    for i = 1, math.ceil(256 / w) do\\n        local row = {niels(P)}\\n        for j = 2, 2 ^ w / 2 do\\n            P = add(P, row[1])\\n            row[j] = niels(P)\\n        end\\n        out[i] = row\\n        P = double(P)\\n    end\\n    return out\\nend\\n\\n--- The radix logarithm of the precomputed table for G.\\nlocal G_W = 5\\n\\n--- The precomputed multiplication table for G.\\nlocal G_TABLE = radixWTable(G, G_W)\\n\\n--- Transforms little-endian bits into a signed radix-2^w non-adjacent form.\\n---\\n--- The returned array contains a 0 whenever a single doubling is needed, or an\\n--- odd integer when an addition with a multiple of the base is needed.\\n---\\n--- @param bits number[]\\n--- @param w number Log2 of the radix, must be at least 1.\\n--- @return number[]\\nlocal function wNaf(bits, w)\\n    -- TODO Find a more elegant way of doing this.\\n    local wPow = 2 ^ w\\n    local wPowh = wPow / 2\\n    local out = {}\\n    local acc = 0\\n    local mul = 1\\n    for i = 1, #bits do\\n        acc = acc + bits[i] * mul\\n        mul = mul * 2\\n        while i == #bits and acc > 0 or mul > wPow do\\n            if acc % 2 == 0 then\\n                acc = acc / 2\\n                mul = mul / 2\\n                out[#out + 1] = 0\\n            else\\n                local rem = acc % wPow\\n                if rem >= wPowh then rem = rem - wPow end\\n                acc = acc - rem\\n                out[#out + 1] = rem\\n            end\\n        end\\n    end\\n    while out[#out] == 0 do out[#out] = nil end\\n    return out\\nend\\n\\n--- Computes a multiplication table for wNAF form multiplication.\\n--- @param P EdPoint The base point.\\n--- @param w number Log2 of the radix, must be at least 1.\\n--- @return NsPoint[]\\nlocal function WNAFTable(P, w)\\n    local dP = double(P)\\n    local out = {niels(P)}\\n    for i = 3, 2 ^ w, 2 do\\n        out[i] = niels(add(dP, out[i - 2]))\\n    end\\n    return out\\nend\\n\\n--- Performs a scalar multiplication by the base point G.\\n--- @param bits number[] The scalar multiplicand little-endian bits.\\n--- @return EdPoint\\nlocal function mulG(bits)\\n    local sw = signedRadixW(bits, G_W)\\n    local R = O\\n    for i = 1, #sw do\\n        local b = sw[i]\\n        if b > 0 then\\n            R = add(R, G_TABLE[i][b])\\n        elseif b < 0 then\\n            R = sub(R, G_TABLE[i][-b])\\n        end\\n    end\\n    return R\\nend\\n\\n--- Performs a scalar multiplication operation.\\n--- @param P EdPoint The base point.\\n--- @param bits number[] The scalar multiplicand little-endian bits.\\n--- @return EdPoint\\nlocal function mul(P, bits)\\n    local naf = wNaf(bits, 5)\\n    local tbl = WNAFTable(P, 5)\\n    local R = O\\n    for i = #naf, 1, -1 do\\n        local b = naf[i]\\n        if b == 0 then\\n            R = double(R)\\n        elseif b > 0 then\\n            R = add(R, tbl[b])\\n        else\\n            R = sub(R, tbl[-b])\\n        end\\n    end\\n    return R\\nend\\n\\nreturn {\\n    double = double,\\n    add = add,\\n    sub = sub,\\n    niels = niels,\\n    scale = scale,\\n    encode = encode,\\n    decode = decode,\\n    mulG = mulG,\\n    mul = mul,\\n}\\n\",\"size\":8845,\"sha256\":\"c943e22757dcf339b847d699bb90662cec5f50c624921aef9fb7ffbbc66f59d8\"},\"ccryptolib/internal/fp.lua\":{\"content\":\"--- Arithmetic on Curve25519's base field.\\n\\nlocal packing = require \\\"ccryptolib.internal.packing\\\"\\n\\nlocal unpack = unpack or table.unpack\\nlocal ufp, fmtfp = packing.compileUnpack(\\\"<I3I3I2I3I3I2I3I3I2I3I3I2\\\")\\n\\n--- @class Fq An element of the field of integers modulo 2\\u00b2\\u2075\\u2075 - 19.\\n\\n--- @class FpR2: Fq An Fp element with limbs inside twice the standard range.\\n\\n--- @class FpR1: FpR2 An Fp element with limbs inside the standard range. See\\n--- the Curve25519 polynomial representation for more info around this.\\n\\n--- The modular square root of -1.\\n--- @type FpR1\\nlocal I = {\\n    0958640 * 2 ^ 0,\\n    0826664 * 2 ^ 22,\\n    1613251 * 2 ^ 43,\\n    1041528 * 2 ^ 64,\\n    0013673 * 2 ^ 85,\\n    0387171 * 2 ^ 107,\\n    1824679 * 2 ^ 128,\\n    0313839 * 2 ^ 149,\\n    0709440 * 2 ^ 170,\\n    0122635 * 2 ^ 192,\\n    0262782 * 2 ^ 213,\\n    0712905 * 2 ^ 234,\\n}\\n\\n--- Converts a Lua number to an element.\\n--- @param n number A number n in [0..2\\u00b2\\u00b2).\\n--- @return FpR1 out The number as an element.\\nlocal function num(n)\\n    return {n, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}\\nend\\n\\n--- Negates an element.\\n--- @param a FpR1\\n--- @return FpR1 out -a.\\nlocal function neg(a)\\n    local a00, a01, a02, a03, a04, a05, a06, a07, a08, a09, a10, a11 = unpack(a)\\n    return {\\n        -a00,\\n        -a01,\\n        -a02,\\n        -a03,\\n        -a04,\\n        -a05,\\n        -a06,\\n        -a07,\\n        -a08,\\n        -a09,\\n        -a10,\\n        -a11,\\n    }\\nend\\n\\n--- Adds two elements.\\n--- @param a FpR1\\n--- @param b FpR1\\n--- @return FpR2 out a + b.\\nlocal function add(a, b)\\n    local a00, a01, a02, a03, a04, a05, a06, a07, a08, a09, a10, a11 = unpack(a)\\n    local b00, b01, b02, b03, b04, b05, b06, b07, b08, b09, b10, b11 = unpack(b)\\n    return {\\n        a00 + b00,\\n        a01 + b01,\\n        a02 + b02,\\n        a03 + b03,\\n        a04 + b04,\\n        a05 + b05,\\n        a06 + b06,\\n        a07 + b07,\\n        a08 + b08,\\n        a09 + b09,\\n        a10 + b10,\\n        a11 + b11,\\n    }\\nend\\n\\n--- Subtracts an element from another.\\n--- @param a FpR1\\n--- @param b FpR1\\n--- @return FpR2 out a - b.\\nlocal function sub(a, b)\\n    local a00, a01, a02, a03, a04, a05, a06, a07, a08, a09, a10, a11 = unpack(a)\\n    local b00, b01, b02, b03, b04, b05, b06, b07, b08, b09, b10, b11 = unpack(b)\\n    return {\\n        a00 - b00,\\n        a01 - b01,\\n        a02 - b02,\\n        a03 - b03,\\n        a04 - b04,\\n        a05 - b05,\\n        a06 - b06,\\n        a07 - b07,\\n        a08 - b08,\\n        a09 - b09,\\n        a10 - b10,\\n        a11 - b11,\\n    }\\nend\\n\\n--- Carries an element. Also performs a small reduction modulo p.\\n--- @param a FpR2 The element to carry.\\n--- @return FpR1 out The same element as a but in a tighter range.\\nlocal function carry(a)\\n    local a00, a01, a02, a03, a04, a05, a06, a07, a08, a09, a10, a11 = unpack(a)\\n    local c00, c01, c02, c03, c04, c05, c06, c07, c08, c09, c10, c11\\n\\n    c11 = a11 + 3 * 2 ^ 306 - 3 * 2 ^ 306  a00 = a00 + 19 / 2 ^ 255 * c11\\n\\n    c00 = a00 + 3 * 2 ^ 73  - 3 * 2 ^ 73   a01 = a01 + c00\\n    c01 = a01 + 3 * 2 ^ 94  - 3 * 2 ^ 94   a02 = a02 + c01\\n    c02 = a02 + 3 * 2 ^ 115 - 3 * 2 ^ 115  a03 = a03 + c02\\n    c03 = a03 + 3 * 2 ^ 136 - 3 * 2 ^ 136  a04 = a04 + c03\\n    c04 = a04 + 3 * 2 ^ 158 - 3 * 2 ^ 158  a05 = a05 + c04\\n    c05 = a05 + 3 * 2 ^ 179 - 3 * 2 ^ 179  a06 = a06 + c05\\n    c06 = a06 + 3 * 2 ^ 200 - 3 * 2 ^ 200  a07 = a07 + c06\\n    c07 = a07 + 3 * 2 ^ 221 - 3 * 2 ^ 221  a08 = a08 + c07\\n    c08 = a08 + 3 * 2 ^ 243 - 3 * 2 ^ 243  a09 = a09 + c08\\n    c09 = a09 + 3 * 2 ^ 264 - 3 * 2 ^ 264  a10 = a10 + c09\\n    c10 = a10 + 3 * 2 ^ 285 - 3 * 2 ^ 285  a11 = a11 - c11 + c10\\n\\n    c11 = a11 + 3 * 2 ^ 306 - 3 * 2 ^ 306\\n\\n    return {\\n        a00 - c00 + 19 / 2 ^ 255 * c11,\\n        a01 - c01,\\n        a02 - c02,\\n        a03 - c03,\\n        a04 - c04,\\n        a05 - c05,\\n        a06 - c06,\\n        a07 - c07,\\n        a08 - c08,\\n        a09 - c09,\\n        a10 - c10,\\n        a11 - c11,\\n    }\\nend\\n\\n--- Returns the canoncal representative of a modp number.\\n---\\n--- Some elements can be represented by two different arrays of floats. This\\n--- returns the canonical element of the represented equivalence class. We\\n--- define an element as canonical if it's the smallest nonnegative number in\\n--- its class.\\n---\\n--- @param a FpR2\\n--- @return FpR1 out A canonical element a' \\u2261 a (mod p).\\nlocal function canonicalize(a)\\n    local a00, a01, a02, a03, a04, a05, a06, a07, a08, a09, a10, a11 = unpack(a)\\n    local c00, c01, c02, c03, c04, c05, c06, c07, c08, c09, c10, c11\\n\\n    -- Perform an euclidean reduction.\\n    -- TODO Range check.\\n    c00 = a00 % 2 ^ 22   a01 = a00 - c00 + a01\\n    c01 = a01 % 2 ^ 43   a02 = a01 - c01 + a02\\n    c02 = a02 % 2 ^ 64   a03 = a02 - c02 + a03\\n    c03 = a03 % 2 ^ 85   a04 = a03 - c03 + a04\\n    c04 = a04 % 2 ^ 107  a05 = a04 - c04 + a05\\n    c05 = a05 % 2 ^ 128  a06 = a05 - c05 + a06\\n    c06 = a06 % 2 ^ 149  a07 = a06 - c06 + a07\\n    c07 = a07 % 2 ^ 170  a08 = a07 - c07 + a08\\n    c08 = a08 % 2 ^ 192  a09 = a08 - c08 + a09\\n    c09 = a09 % 2 ^ 213  a10 = a09 - c09 + a10\\n    c10 = a10 % 2 ^ 234  a11 = a10 - c10 + a11\\n    c11 = a11 % 2 ^ 255  c00 = c00 + 19 / 2 ^ 255 * (a11 - c11)\\n\\n    -- Canonicalize.\\n    if      c11 / 2 ^ 234 == 2 ^ 21 - 1\\n        and c10 / 2 ^ 213 == 2 ^ 21 - 1\\n        and c09 / 2 ^ 192 == 2 ^ 21 - 1\\n        and c08 / 2 ^ 170 == 2 ^ 22 - 1\\n        and c07 / 2 ^ 149 == 2 ^ 21 - 1\\n        and c06 / 2 ^ 128 == 2 ^ 21 - 1\\n        and c05 / 2 ^ 107 == 2 ^ 21 - 1\\n        and c04 / 2 ^ 85  == 2 ^ 22 - 1\\n        and c03 / 2 ^ 64  == 2 ^ 21 - 1\\n        and c02 / 2 ^ 43  == 2 ^ 21 - 1\\n        and c01 / 2 ^ 22  == 2 ^ 21 - 1\\n        and c00 >= 2 ^ 22 - 19\\n    then\\n        return {19 - 2 ^ 22 + c00, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}\\n    else\\n        return {c00, c01, c02, c03, c04, c05, c06, c07, c08, c09, c10, c11}\\n    end\\nend\\n\\n--- Returns whether two elements are the same.\\n--- @param a FpR1\\n--- @param b FpR1\\n--- @return boolean eq Whether a \\u2261 b (mod p).\\nlocal function eq(a, b)\\n    local c = canonicalize(sub(a, b))\\n    for i = 1, 12 do if c[i] ~= 0 then return false end end\\n    return true\\nend\\n\\n--- Multiplies two elements.\\n--- @param a FpR2\\n--- @param b FpR2\\n--- @return FpR1 c An element such that c \\u2261 a \\u00d7 b (mod p).\\nlocal function mul(a, b)\\n    local a00, a01, a02, a03, a04, a05, a06, a07, a08, a09, a10, a11 = unpack(a)\\n    local b00, b01, b02, b03, b04, b05, b06, b07, b08, b09, b10, b11 = unpack(b)\\n    local c00, c01, c02, c03, c04, c05, c06, c07, c08, c09, c10, c11\\n\\n    -- Multiply high half into c00..c11.\\n    c00 = a11 * b01\\n        + a10 * b02\\n        + a09 * b03\\n        + a08 * b04\\n        + a07 * b05\\n        + a06 * b06\\n        + a05 * b07\\n        + a04 * b08\\n        + a03 * b09\\n        + a02 * b10\\n        + a01 * b11\\n    c01 = a11 * b02\\n        + a10 * b03\\n        + a09 * b04\\n        + a08 * b05\\n        + a07 * b06\\n        + a06 * b07\\n        + a05 * b08\\n        + a04 * b09\\n        + a03 * b10\\n        + a02 * b11\\n    c02 = a11 * b03\\n        + a10 * b04\\n        + a09 * b05\\n        + a08 * b06\\n        + a07 * b07\\n        + a06 * b08\\n        + a05 * b09\\n        + a04 * b10\\n        + a03 * b11\\n    c03 = a11 * b04\\n        + a10 * b05\\n        + a09 * b06\\n        + a08 * b07\\n        + a07 * b08\\n        + a06 * b09\\n        + a05 * b10\\n        + a04 * b11\\n    c04 = a11 * b05\\n        + a10 * b06\\n        + a09 * b07\\n        + a08 * b08\\n        + a07 * b09\\n        + a06 * b10\\n        + a05 * b11\\n    c05 = a11 * b06\\n        + a10 * b07\\n        + a09 * b08\\n        + a08 * b09\\n        + a07 * b10\\n        + a06 * b11\\n    c06 = a11 * b07\\n        + a10 * b08\\n        + a09 * b09\\n        + a08 * b10\\n        + a07 * b11\\n    c07 = a11 * b08\\n        + a10 * b09\\n        + a09 * b10\\n        + a08 * b11\\n    c08 = a11 * b09\\n        + a10 * b10\\n        + a09 * b11\\n    c09 = a11 * b10\\n        + a10 * b11\\n    c10 = a11 * b11\\n\\n    -- Multiply low half with reduction into c00..c11.\\n    c00 = c00 * (19 / 2 ^ 255)\\n        + a00 * b00\\n    c01 = c01 * (19 / 2 ^ 255)\\n        + a01 * b00\\n        + a00 * b01\\n    c02 = c02 * (19 / 2 ^ 255)\\n        + a02 * b00\\n        + a01 * b01\\n        + a00 * b02\\n    c03 = c03 * (19 / 2 ^ 255)\\n        + a03 * b00\\n        + a02 * b01\\n        + a01 * b02\\n        + a00 * b03\\n    c04 = c04 * (19 / 2 ^ 255)\\n        + a04 * b00\\n        + a03 * b01\\n        + a02 * b02\\n        + a01 * b03\\n        + a00 * b04\\n    c05 = c05 * (19 / 2 ^ 255)\\n        + a05 * b00\\n        + a04 * b01\\n        + a03 * b02\\n        + a02 * b03\\n        + a01 * b04\\n        + a00 * b05\\n    c06 = c06 * (19 / 2 ^ 255)\\n        + a06 * b00\\n        + a05 * b01\\n        + a04 * b02\\n        + a03 * b03\\n        + a02 * b04\\n        + a01 * b05\\n        + a00 * b06\\n    c07 = c07 * (19 / 2 ^ 255)\\n        + a07 * b00\\n        + a06 * b01\\n        + a05 * b02\\n        + a04 * b03\\n        + a03 * b04\\n        + a02 * b05\\n        + a01 * b06\\n        + a00 * b07\\n    c08 = c08 * (19 / 2 ^ 255)\\n        + a08 * b00\\n        + a07 * b01\\n        + a06 * b02\\n        + a05 * b03\\n        + a04 * b04\\n        + a03 * b05\\n        + a02 * b06\\n        + a01 * b07\\n        + a00 * b08\\n    c09 = c09 * (19 / 2 ^ 255)\\n        + a09 * b00\\n        + a08 * b01\\n        + a07 * b02\\n        + a06 * b03\\n        + a05 * b04\\n        + a04 * b05\\n        + a03 * b06\\n        + a02 * b07\\n        + a01 * b08\\n        + a00 * b09\\n    c10 = c10 * (19 / 2 ^ 255)\\n        + a10 * b00\\n        + a09 * b01\\n        + a08 * b02\\n        + a07 * b03\\n        + a06 * b04\\n        + a05 * b05\\n        + a04 * b06\\n        + a03 * b07\\n        + a02 * b08\\n        + a01 * b09\\n        + a00 * b10\\n    c11 = a11 * b00\\n        + a10 * b01\\n        + a09 * b02\\n        + a08 * b03\\n        + a07 * b04\\n        + a06 * b05\\n        + a05 * b06\\n        + a04 * b07\\n        + a03 * b08\\n        + a02 * b09\\n        + a01 * b10\\n        + a00 * b11\\n\\n    -- Carry and reduce.\\n    a10 = c10 + 3 * 2 ^ 285 - 3 * 2 ^ 285  c11 = c11 + a10\\n    a11 = c11 + 3 * 2 ^ 306 - 3 * 2 ^ 306  c00 = c00 + 19 / 2 ^ 255 * a11\\n\\n    a00 = c00 + 3 * 2 ^ 73  - 3 * 2 ^ 73   c01 = c01 + a00\\n    a01 = c01 + 3 * 2 ^ 94  - 3 * 2 ^ 94   c02 = c02 + a01\\n    a02 = c02 + 3 * 2 ^ 115 - 3 * 2 ^ 115  c03 = c03 + a02\\n    a03 = c03 + 3 * 2 ^ 136 - 3 * 2 ^ 136  c04 = c04 + a03\\n    a04 = c04 + 3 * 2 ^ 158 - 3 * 2 ^ 158  c05 = c05 + a04\\n    a05 = c05 + 3 * 2 ^ 179 - 3 * 2 ^ 179  c06 = c06 + a05\\n    a06 = c06 + 3 * 2 ^ 200 - 3 * 2 ^ 200  c07 = c07 + a06\\n    a07 = c07 + 3 * 2 ^ 221 - 3 * 2 ^ 221  c08 = c08 + a07\\n    a08 = c08 + 3 * 2 ^ 243 - 3 * 2 ^ 243  c09 = c09 + a08\\n    a09 = c09 + 3 * 2 ^ 264 - 3 * 2 ^ 264  c10 = c10 - a10 + a09\\n    a10 = c10 + 3 * 2 ^ 285 - 3 * 2 ^ 285  c11 = c11 - a11 + a10\\n\\n    a11 = c11 + 3 * 2 ^ 306 - 3 * 2 ^ 306\\n\\n    return {\\n        c00 - a00 + 19 / 2 ^ 255 * a11,\\n        c01 - a01,\\n        c02 - a02,\\n        c03 - a03,\\n        c04 - a04,\\n        c05 - a05,\\n        c06 - a06,\\n        c07 - a07,\\n        c08 - a08,\\n        c09 - a09,\\n        c10 - a10,\\n        c11 - a11,\\n    }\\nend\\n\\n--- Squares an element.\\n--- @param a FpR2\\n--- @return FpR1 b An element such that b \\u2261 a\\u00b2 (mod p).\\nlocal function square(a)\\n    local a00, a01, a02, a03, a04, a05, a06, a07, a08, a09, a10, a11 = unpack(a)\\n    local d00, d01, d02, d03, d04, d05, d06, d07, d08, d09, d10\\n    local c00, c01, c02, c03, c04, c05, c06, c07, c08, c09, c10, c11\\n\\n    -- Compute 2a.\\n    d00 = a00 + a00\\n    d01 = a01 + a01\\n    d02 = a02 + a02\\n    d03 = a03 + a03\\n    d04 = a04 + a04\\n    d05 = a05 + a05\\n    d06 = a06 + a06\\n    d07 = a07 + a07\\n    d08 = a08 + a08\\n    d09 = a09 + a09\\n    d10 = a10 + a10\\n\\n    -- Multiply high half into c00..c11.\\n    c00 = a11 * d01\\n        + a10 * d02\\n        + a09 * d03\\n        + a08 * d04\\n        + a07 * d05\\n        + a06 * a06\\n    c01 = a11 * d02\\n        + a10 * d03\\n        + a09 * d04\\n        + a08 * d05\\n        + a07 * d06\\n    c02 = a11 * d03\\n        + a10 * d04\\n        + a09 * d05\\n        + a08 * d06\\n        + a07 * a07\\n    c03 = a11 * d04\\n        + a10 * d05\\n        + a09 * d06\\n        + a08 * d07\\n    c04 = a11 * d05\\n        + a10 * d06\\n        + a09 * d07\\n        + a08 * a08\\n    c05 = a11 * d06\\n        + a10 * d07\\n        + a09 * d08\\n    c06 = a11 * d07\\n        + a10 * d08\\n        + a09 * a09\\n    c07 = a11 * d08\\n        + a10 * d09\\n    c08 = a11 * d09\\n        + a10 * a10\\n    c09 = a11 * d10\\n    c10 = a11 * a11\\n\\n    -- Multiply low half with reduction into c00..c11.\\n    c00 = c00 * (19 / 2 ^ 255)\\n        + a00 * a00\\n    c01 = c01 * (19 / 2 ^ 255)\\n        + a01 * d00\\n    c02 = c02 * (19 / 2 ^ 255)\\n        + a02 * d00\\n        + a01 * a01\\n    c03 = c03 * (19 / 2 ^ 255)\\n        + a03 * d00\\n        + a02 * d01\\n    c04 = c04 * (19 / 2 ^ 255)\\n        + a04 * d00\\n        + a03 * d01\\n        + a02 * a02\\n    c05 = c05 * (19 / 2 ^ 255)\\n        + a05 * d00\\n        + a04 * d01\\n        + a03 * d02\\n    c06 = c06 * (19 / 2 ^ 255)\\n        + a06 * d00\\n        + a05 * d01\\n        + a04 * d02\\n        + a03 * a03\\n    c07 = c07 * (19 / 2 ^ 255)\\n        + a07 * d00\\n        + a06 * d01\\n        + a05 * d02\\n        + a04 * d03\\n    c08 = c08 * (19 / 2 ^ 255)\\n        + a08 * d00\\n        + a07 * d01\\n        + a06 * d02\\n        + a05 * d03\\n        + a04 * a04\\n    c09 = c09 * (19 / 2 ^ 255)\\n        + a09 * d00\\n        + a08 * d01\\n        + a07 * d02\\n        + a06 * d03\\n        + a05 * d04\\n    c10 = c10 * (19 / 2 ^ 255)\\n        + a10 * d00\\n        + a09 * d01\\n        + a08 * d02\\n        + a07 * d03\\n        + a06 * d04\\n        + a05 * a05\\n    c11 = a11 * d00\\n        + a10 * d01\\n        + a09 * d02\\n        + a08 * d03\\n        + a07 * d04\\n        + a06 * d05\\n\\n    -- Carry and reduce.\\n    a10 = c10 + 3 * 2 ^ 285 - 3 * 2 ^ 285  c11 = c11 + a10\\n    a11 = c11 + 3 * 2 ^ 306 - 3 * 2 ^ 306  c00 = c00 + 19 / 2 ^ 255 * a11\\n\\n    a00 = c00 + 3 * 2 ^ 73  - 3 * 2 ^ 73   c01 = c01 + a00\\n    a01 = c01 + 3 * 2 ^ 94  - 3 * 2 ^ 94   c02 = c02 + a01\\n    a02 = c02 + 3 * 2 ^ 115 - 3 * 2 ^ 115  c03 = c03 + a02\\n    a03 = c03 + 3 * 2 ^ 136 - 3 * 2 ^ 136  c04 = c04 + a03\\n    a04 = c04 + 3 * 2 ^ 158 - 3 * 2 ^ 158  c05 = c05 + a04\\n    a05 = c05 + 3 * 2 ^ 179 - 3 * 2 ^ 179  c06 = c06 + a05\\n    a06 = c06 + 3 * 2 ^ 200 - 3 * 2 ^ 200  c07 = c07 + a06\\n    a07 = c07 + 3 * 2 ^ 221 - 3 * 2 ^ 221  c08 = c08 + a07\\n    a08 = c08 + 3 * 2 ^ 243 - 3 * 2 ^ 243  c09 = c09 + a08\\n    a09 = c09 + 3 * 2 ^ 264 - 3 * 2 ^ 264  c10 = c10 - a10 + a09\\n    a10 = c10 + 3 * 2 ^ 285 - 3 * 2 ^ 285  c11 = c11 - a11 + a10\\n\\n    a11 = c11 + 3 * 2 ^ 306 - 3 * 2 ^ 306\\n\\n    return {\\n        c00 - a00 + 19 / 2 ^ 255 * a11,\\n        c01 - a01,\\n        c02 - a02,\\n        c03 - a03,\\n        c04 - a04,\\n        c05 - a05,\\n        c06 - a06,\\n        c07 - a07,\\n        c08 - a08,\\n        c09 - a09,\\n        c10 - a10,\\n        c11 - a11,\\n    }\\nend\\n\\n--- Multiplies an element by a number.\\n--- @param a FpR2\\n--- @param k number A number in [0..2\\u00b2\\u00b2).\\n--- @return FpR1 c An element such that c \\u2261 a \\u00d7 k (mod p).\\nlocal function kmul(a, k)\\n    local a00, a01, a02, a03, a04, a05, a06, a07, a08, a09, a10, a11 = unpack(a)\\n    local c00, c01, c02, c03, c04, c05, c06, c07, c08, c09, c10, c11\\n\\n    -- TODO Range check.\\n    a00 = a00 * k\\n    a01 = a01 * k\\n    a02 = a02 * k\\n    a03 = a03 * k\\n    a04 = a04 * k\\n    a05 = a05 * k\\n    a06 = a06 * k\\n    a07 = a07 * k\\n    a08 = a08 * k\\n    a09 = a09 * k\\n    a10 = a10 * k\\n    a11 = a11 * k\\n\\n    c11 = a11 + 3 * 2 ^ 306 - 3 * 2 ^ 306  a00 = a00 + 19 / 2 ^ 255 * c11\\n\\n    c00 = a00 + 3 * 2 ^ 73  - 3 * 2 ^ 73   a01 = a01 + c00\\n    c01 = a01 + 3 * 2 ^ 94  - 3 * 2 ^ 94   a02 = a02 + c01\\n    c02 = a02 + 3 * 2 ^ 115 - 3 * 2 ^ 115  a03 = a03 + c02\\n    c03 = a03 + 3 * 2 ^ 136 - 3 * 2 ^ 136  a04 = a04 + c03\\n    c04 = a04 + 3 * 2 ^ 158 - 3 * 2 ^ 158  a05 = a05 + c04\\n    c05 = a05 + 3 * 2 ^ 179 - 3 * 2 ^ 179  a06 = a06 + c05\\n    c06 = a06 + 3 * 2 ^ 200 - 3 * 2 ^ 200  a07 = a07 + c06\\n    c07 = a07 + 3 * 2 ^ 221 - 3 * 2 ^ 221  a08 = a08 + c07\\n    c08 = a08 + 3 * 2 ^ 243 - 3 * 2 ^ 243  a09 = a09 + c08\\n    c09 = a09 + 3 * 2 ^ 264 - 3 * 2 ^ 264  a10 = a10 + c09\\n    c10 = a10 + 3 * 2 ^ 285 - 3 * 2 ^ 285  a11 = a11 - c11 + c10\\n\\n    c11 = a11 + 3 * 2 ^ 306 - 3 * 2 ^ 306\\n\\n    return {\\n        a00 - c00 + 19 / 2 ^ 255 * c11,\\n        a01 - c01,\\n        a02 - c02,\\n        a03 - c03,\\n        a04 - c04,\\n        a05 - c05,\\n        a06 - c06,\\n        a07 - c07,\\n        a08 - c08,\\n        a09 - c09,\\n        a10 - c10,\\n        a11 - c11\\n    }\\nend\\n\\n--- Squares an element n times.\\n--- @param a FpR2\\n--- @param n number The number of times to square a.\\n--- @return FpR1 c A number c such that c \\u2261 a ^ 2 ^ n (mod p).\\nlocal function nsquare(a, n)\\n    for _ = 1, n do a = square(a) end\\n    return a\\nend\\n\\n--- Computes the inverse of an element.\\n---\\n--- Performance: 11 multiplications and 252 squarings.\\n---\\n--- @param a FpR2\\n--- @return FpR1 c An element such that c \\u2261 a\\u207b\\u00b9 (mod p), or 0 if c doesn't exist.\\nlocal function invert(a)\\n    local a2 = square(a)\\n    local a9 = mul(a, nsquare(a2, 2))\\n    local a11 = mul(a9, a2)\\n\\n    local x5 = mul(square(a11), a9)\\n    local x10 = mul(nsquare(x5, 5), x5)\\n    local x20 = mul(nsquare(x10, 10), x10)\\n    local x40 = mul(nsquare(x20, 20), x20)\\n    local x50 = mul(nsquare(x40, 10), x10)\\n    local x100 = mul(nsquare(x50, 50), x50)\\n    local x200 = mul(nsquare(x100, 100), x100)\\n    local x250 = mul(nsquare(x200, 50), x50)\\n\\n    return mul(nsquare(x250, 5), a11)\\nend\\n\\n--- Returns an element x that satisfies vx\\u00b2 = u.\\n---\\n--- Note that when v = 0, the returned element can take any value.\\n---\\n--- @param u FpR2\\n--- @param v FpR2\\n--- @return FpR1? x An element such that vx\\u00b2 \\u2261 u (mod p), if it exists.\\nlocal function sqrtDiv(u, v)\\n    u = carry(u)\\n\\n    local v2 = square(v)\\n    local v3 = mul(v, v2)\\n    local v6 = square(v3)\\n    local v7 = mul(v, v6)\\n    local uv7 = mul(u, v7)\\n\\n    local x2 = mul(square(uv7), uv7)\\n    local x4 = mul(nsquare(x2, 2), x2)\\n    local x8 = mul(nsquare(x4, 4), x4)\\n    local x16 = mul(nsquare(x8, 8), x8)\\n    local x18 = mul(nsquare(x16, 2), x2)\\n    local x32 = mul(nsquare(x16, 16), x16)\\n    local x50 = mul(nsquare(x32, 18), x18)\\n    local x100 = mul(nsquare(x50, 50), x50)\\n    local x200 = mul(nsquare(x100, 100), x100)\\n    local x250 = mul(nsquare(x200, 50), x50)\\n    local pr = mul(nsquare(x250, 2), uv7)\\n\\n    local uv3 = mul(u, v3)\\n    local b = mul(uv3, pr)\\n    local b2 = square(b)\\n    local vb2 = mul(v, b2)\\n\\n    if not eq(vb2, u) then\\n        -- Found sqrt(-u/v), multiply by i.\\n        b = mul(b, I)\\n        b2 = square(b)\\n        vb2 = mul(v, b2)\\n    end\\n\\n    if eq(vb2, u) then\\n        return b\\n    else\\n        return nil\\n    end\\nend\\n\\n--- @class String32: string A string with length equal to 32 bytes.\\n\\n--- Encodes an element in little-endian.\\n--- @param a FpR1\\n--- @return String32 out The 32-byte canonical encoding of a.\\nlocal function encode(a)\\n    a = canonicalize(a)\\n    local a00, a01, a02, a03, a04, a05, a06, a07, a08, a09, a10, a11 = unpack(a)\\n\\n    local bytes = {}\\n    local acc = a00\\n\\n    local function putBytes(n)\\n        for _ = 1, n do\\n            local byte = acc % 256\\n            bytes[#bytes + 1] = byte\\n            acc = (acc - byte) / 256\\n        end\\n    end\\n\\n    putBytes(2) acc = acc + a01 / 2 ^ 16\\n    putBytes(3) acc = acc + a02 / 2 ^ 40\\n    putBytes(3) acc = acc + a03 / 2 ^ 64\\n    putBytes(2) acc = acc + a04 / 2 ^ 80\\n    putBytes(3) acc = acc + a05 / 2 ^ 104\\n    putBytes(3) acc = acc + a06 / 2 ^ 128\\n    putBytes(2) acc = acc + a07 / 2 ^ 144\\n    putBytes(3) acc = acc + a08 / 2 ^ 168\\n    putBytes(3) acc = acc + a09 / 2 ^ 192\\n    putBytes(2) acc = acc + a10 / 2 ^ 208\\n    putBytes(3) acc = acc + a11 / 2 ^ 232\\n    putBytes(3)\\n\\n    return string.char(unpack(bytes)) --[[@as String32, putBytes sums to 32]]\\nend\\n\\n--- Decodes an element in little-endian.\\n--- @param b String32 A 32-byte string, the most-significant bit is discarded.\\n--- @return FpR1 out The decoded element. It may not be canonical.\\nlocal function decode(b)\\n    local w00, w01, w02, w03, w04, w05, w06, w07, w08, w09, w10, w11 =\\n        ufp(fmtfp, b, 1)\\n\\n    w11 = w11 % 2 ^ 15\\n\\n    return carry {\\n        w00,\\n        w01 * 2 ^ 24,\\n        w02 * 2 ^ 48,\\n        w03 * 2 ^ 64,\\n        w04 * 2 ^ 88,\\n        w05 * 2 ^ 112,\\n        w06 * 2 ^ 128,\\n        w07 * 2 ^ 152,\\n        w08 * 2 ^ 176,\\n        w09 * 2 ^ 192,\\n        w10 * 2 ^ 216,\\n        w11 * 2 ^ 240,\\n    }\\nend\\n\\n--- Checks if the given element is equal to 0.\\n--- @param a FpR2\\n--- @return boolean eqz Whether a \\u2261 0 (mod p).\\nlocal function eqz(a)\\n    local c = canonicalize(a)\\n    local c00, c01, c02, c03, c04, c05, c06, c07, c08, c09, c10, c11 = unpack(c)\\n    return c00 + c01 + c02 + c03 + c04 + c05 + c06 + c07 + c08 + c09 + c10 + c11\\n        == 0\\nend\\n\\nreturn {\\n    num = num,\\n    neg = neg,\\n    add = add,\\n    sub = sub,\\n    kmul = kmul,\\n    mul = mul,\\n    canonicalize = canonicalize,\\n    square = square,\\n    carry = carry,\\n    invert = invert,\\n    sqrtDiv = sqrtDiv,\\n    encode = encode,\\n    decode = decode,\\n    eqz = eqz,\\n}\\n\",\"size\":20753,\"sha256\":\"543ac4a1f9cb374a707ed63f94ff32b0341e495ccbb8c6ef5d8a936039cd7c08\"},\"ccryptolib/internal/fq.lua\":{\"content\":\"--- Arithmetic on Curve25519's scalar field.\\n\\nlocal mp = require \\\"ccryptolib.internal.mp\\\"\\nlocal util = require \\\"ccryptolib.internal.util\\\"\\nlocal packing = require \\\"ccryptolib.internal.packing\\\"\\n\\nlocal unpack = unpack or table.unpack\\nlocal pfq, fmtfq = packing.compilePack(\\\"<I3I3I3I3I3I3I3I3I3I3I2\\\")\\nlocal ufq = packing.compileUnpack(fmtfq)\\nlocal ufql, fmtfql = packing.compileUnpack(\\\"<I3I3I3I3I3I3I3I3I3I3I3\\\")\\nlocal ufqh, fmtfqh = packing.compileUnpack(\\\"<I3I3I3I3I3I3I3I3I3I3I1\\\")\\n\\n--- The scalar field's order, q = 2\\u00b2\\u2075\\u00b2 + 27742317777372353535851937790883648493.\\nlocal Q = {\\n    16110573,\\n    06494812,\\n    14047250,\\n    10680220,\\n    14612958,\\n    00000020,\\n    00000000,\\n    00000000,\\n    00000000,\\n    00000000,\\n    00004096,\\n}\\n\\n--- The first Montgomery precomputed constant, -q\\u207b\\u00b9 mod 2\\u00b2\\u2076\\u2074.\\nlocal T0 = {\\n    05537307,\\n    01942290,\\n    16765621,\\n    16628356,\\n    10618610,\\n    07072433,\\n    03735459,\\n    01369940,\\n    15276086,\\n    13038191,\\n    13409718,\\n}\\n\\n--- The second Montgomery precomputed constant, 2\\u2075\\u00b2\\u2078 mod q.\\nlocal T1 = {\\n    11711996,\\n    01747860,\\n    08326961,\\n    03814718,\\n    01859974,\\n    13327461,\\n    16105061,\\n    07590423,\\n    04050668,\\n    08138906,\\n    00000283,\\n}\\n\\nlocal T8 = {\\n    5110253,\\n    3039345,\\n    2503500,\\n    11779568,\\n    15416472,\\n    16766550,\\n    16777215,\\n    16777215,\\n    16777215,\\n    16777215,\\n    4095,\\n}\\n\\nlocal ZERO = mp.num(0)\\n\\n--- Reduces a number modulo q.\\n--\\n-- @tparam {number...} a A number a < 2q as 11 limbs in [0..2\\u00b2\\u2075).\\n-- @treturn {number...} a mod q as 11 limbs in [0..2\\u00b2\\u2074).\\n--\\nlocal function reduce(a)\\n    local c = mp.sub(a, Q)\\n    local out, overflow = mp.carry(c)\\n\\n    -- Return carry(a) if a < q.\\n    if overflow < 0 then return (mp.carry(a)) end\\n\\n    -- c >= q means c - q >= 0.\\n    -- Since q < 2\\u00b2\\u2078\\u2078, c < 2q means c - q < q < 2\\u00b2\\u2078\\u2078.\\n    -- c's limbs fit in (-2\\u00b2\\u2076..2\\u00b2\\u2076), since subtraction adds at most one bit.\\n    return out -- cc < q implies that the carry number is 0.\\nend\\n\\n--- Adds two scalars mod q.\\n--\\n-- If the two operands are in Montgomery form, returns the correct result also\\n-- in Montgomery form, since (2\\u00b2\\u2076\\u2074 \\u00d7 a) + (2\\u00b2\\u2076\\u2074 \\u00d7 b) \\u2261 2\\u00b2\\u2076\\u2074 \\u00d7 (a + b) (mod q).\\n--\\n-- @tparam {number...} a A number a < q as 11 limbs in [0..2\\u00b2\\u2074).\\n-- @tparam {number...} b A number b < q as 11 limbs in [0..2\\u00b2\\u2074).\\n-- @treturn {number...} a + b mod q as 11 limbs in [0..2\\u00b2\\u2074).\\n--\\nlocal function add(a, b)\\n    return reduce(mp.add(a, b))\\nend\\n\\n--- Negates a scalar mod q.\\n--\\n-- @tparam {number...} a A number a < q as 11 limbs in [0..2\\u00b2\\u2074).\\n-- @treturn {number...} -a mod q as 11 limbs in [0..2\\u00b2\\u2074).\\n--\\nlocal function neg(a)\\n    return reduce(mp.sub(Q, a))\\nend\\n\\n--- Subtracts scalars mod q.\\n--\\n-- If the two operands are in Montgomery form, returns the correct result also\\n-- in Montgomery form, since (2\\u00b2\\u2076\\u2074 \\u00d7 a) - (2\\u00b2\\u2076\\u2074 \\u00d7 b) \\u2261 2\\u00b2\\u2076\\u2074 \\u00d7 (a - b) (mod q).\\n--\\n-- @tparam {number...} a A number a < q as 11 limbs in [0..2\\u00b2\\u2074).\\n-- @tparam {number...} b A number b < q as 11 limbs in [0..2\\u00b2\\u2074).\\n-- @treturn {number...} a - b mod q as 11 limbs in [0..2\\u00b2\\u2074).\\n--\\nlocal function sub(a, b)\\n    return add(a, neg(b))\\nend\\n\\n--- Given two scalars a and b, computes 2\\u207b\\u00b2\\u2076\\u2074 \\u00d7 a \\u00d7 b mod q.\\n--\\n-- @tparam {number...} a A number a as 11 limbs in [0..2\\u00b2\\u2074).\\n-- @tparam {number...} b A number b < q as 11 limbs in [0..2\\u00b2\\u2074).\\n-- @treturn {number...} 2\\u207b\\u00b2\\u2076\\u2074 \\u00d7 a \\u00d7 b mod q as 11 limbs in [0..2\\u00b2\\u2074).\\n--\\nlocal function mul(a, b)\\n    local t0, t1 = mp.mul(a, b)\\n    local mq0, mq1 = mp.mul(mp.lmul(t0, T0), Q)\\n    local _, s1 = mp.dwadd(t0, t1, mq0, mq1)\\n    return reduce(s1)\\nend\\n\\n--- Converts a scalar into Montgomery form.\\n--\\n-- @tparam {number...} a A number a as 11 limbs in [0..2\\u00b2\\u2074).\\n-- @treturn {number...} 2\\u00b2\\u2076\\u2074 \\u00d7 a mod q as 11 limbs in [0..2\\u00b2\\u2074).\\n--\\nlocal function montgomery(a)\\n    -- 0 \\u2264 a < 2\\u00b2\\u2076\\u2074 and 0 \\u2264 T1 < q.\\n    return mul(a, T1)\\nend\\n\\n--- Converts a scalar from Montgomery form.\\n--\\n-- @tparam {number...} a A number a < q as 11 limbs in [0..2\\u00b2\\u2074).\\n-- @treturn {number...} 2\\u207b\\u00b2\\u2076\\u2074 \\u00d7 a mod q as 11 limbs in [0..2\\u00b2\\u2074).\\n--\\nlocal function demontgomery(a)\\n    -- It's REDC all over again except b is 1.\\n    local mq0, mq1 = mp.mul(mp.lmul(a, T0), Q)\\n    local _, s1 = mp.dwadd(a, ZERO, mq0, mq1)\\n    return reduce(s1)\\nend\\n\\n--- Encodes a scalar.\\n--\\n-- @tparam {number...} a A number 2\\u00b2\\u2076\\u2074 \\u00d7 a mod q as 11 limbs in [0..2\\u00b2\\u2074).\\n-- @treturn string The 32-byte string encoding of a.\\n--\\nlocal function encode(a)\\n    return pfq(fmtfq, unpack(demontgomery(a)))\\nend\\n\\n--- Decodes a scalar.\\n--\\n-- @tparam string str A 32-byte string encoding some little-endian number a.\\n-- @treturn {number...} 2\\u00b2\\u2076\\u2074 \\u00d7 a mod q as 11 limbs in [0..2\\u00b2\\u2074).\\n--\\nlocal function decode(str)\\n    local dec = {ufq(fmtfq, str, 1)} dec[12] = nil\\n    return montgomery(dec)\\nend\\n\\n--- Decodes a scalar from a \\\"wide\\\" string.\\n--\\n-- @tparam string str A 64-byte string encoding some little-endian number a.\\n-- @treturn {number...} 2\\u00b2\\u2076\\u2074 \\u00d7 a mod q as 11 limbs in [0..2\\u00b2\\u2074).\\n--\\nlocal function decodeWide(str)\\n    local low = {ufql(fmtfql, str, 1)} low[12] = nil\\n    local high = {ufqh(fmtfqh, str, 34)} high[12] = nil\\n    return add(montgomery(low), montgomery(montgomery(high)))\\nend\\n\\n--- Decodes a scalar using the X25519/Ed25519 bit clamping scheme.\\n--\\n-- @tparam string str A 32-byte string encoding some little-endian number a.\\n-- @treturn {number...} 2\\u00b2\\u2076\\u2074 \\u00d7 clamp(a) mod q as 11 limbs in [0..2\\u00b2\\u2074).\\n--\\nlocal function decodeClamped(str)\\n    -- Decode.\\n    local words = {ufq(fmtfq, str, 1)} words[12] = nil\\n\\n    -- Clamp.\\n    words[1] = bit32.band(words[1], 0xfffff8)\\n    words[11] = bit32.band(words[11], 0x7fff)\\n    words[11] = bit32.bor(words[11], 0x4000)\\n\\n    return montgomery(words)\\nend\\n\\n--- Divides a scalar by 8.\\n--\\n-- @tparam {number...} 2\\u00b2\\u2076\\u2074 \\u00d7 a mod q as 11 limbs in [0..2\\u00b2\\u2074).\\n-- @treturn {number...} 2\\u00b2\\u2076\\u2075 \\u00d7 a \\u00f7 8 mod q as 11 limbs in [0..2\\u00b2\\u2074).\\nlocal function eighth(a)\\n    return mul(a, T8)\\nend\\n\\n--- Encodes a scalar for round-trip with dedcodeClamped.\\n--\\n-- Only values that are in the decodeClamped range can be re-encoded like this.\\n--\\n-- @tparam {number...} a A number 2\\u00b2\\u2076\\u2074 \\u00d7 a mod q as 11 limbs in [0..2\\u00b2\\u2074).\\n-- @treturn string The 32-byte string encoding of 8(a \\u00f7 8 mod q).\\n--\\nlocal function encodeClamped(a)\\n    local c1 = demontgomery(eighth(a))\\n    local c2 = mp.lmul(c1, mp.num(8))\\n    return pfq(fmtfq, unpack(c2))\\nend\\n\\n--- Returns a scalar in binary.\\n--\\n-- @tparam {number...} a A number a < q as 11 limbs in [0..2\\u00b2\\u2074).\\n-- @treturn {number...} 2\\u207b\\u00b2\\u2076\\u2074 \\u00d7 a mod q as 253 bits.\\n--\\nlocal function bits(a)\\n    local out = util.rebaseLE(demontgomery(a), 2 ^ 24, 2)\\n    for i = 254, 289 do out[i] = nil end\\n    return out\\nend\\n\\n--- Makes a PRAC ruleset from a pair of scalars.\\n--\\n-- For more information see section 3.3 of Speeding up subgroup cryptosystems:\\n-- Martijn Stam. Speeding up subgroup cryptosystems. PhD thesis, Technische\\n-- Universiteit Eindhoven, 2003. https://dx.doi.org/10.6100/IR564670.\\n--\\n-- @tparam {number...} a A scalar 2\\u00b2\\u2076\\u2074 \\u00d7 a mod q as 11 limbs in [0..2\\u00b2\\u2074).\\n-- @tparam {number...} b A scalar 2\\u00b2\\u2076\\u2074 \\u00d7 b mod q as 11 limbs in [0..2\\u00b2\\u2074).\\n-- @treturn {{number...}, {number...}} The generated ruleset.\\n--\\nlocal function makeRuleset(a, b)\\n    -- The numbers in raw multiprecision tables.\\n    ---@type MpSW11L24\\n    local dt = demontgomery(a) -- (-2\\u00b2\\u2074..2\\u00b2\\u2074)\\n    ---@type MpSW11L24\\n    local et = demontgomery(b) -- (-2\\u00b2\\u2074..2\\u00b2\\u2074)\\n    ---@type MpSW11L24\\n    local ft = mp.carryWeak(mp.sub(dt, et))  -- (-2\\u00b2\\u2074..2\\u00b2\\u2074)\\n\\n    -- Residue classes of (d, e) modulo 2.\\n    local d2 = mp.mod2(dt)\\n    local e2 = mp.mod2(et)\\n\\n    -- Residue classes of (d, e) modulo 3.\\n    local d3 = mp.mod3(dt)\\n    local e3 = mp.mod3(et)\\n\\n    -- (e, d - e) in limited-precision floating-point numbers.\\n    local ef = mp.approx(et)\\n    local ff = mp.approx(ft)\\n\\n    -- Lookup table for inversions and halvings modulo 3.\\n    local lut3 = {[0] = 0, 2, 1}\\n\\n    local rules = {}\\n    while true do\\n        local cmp = mp.cmp(mp.carry(dt), mp.carry(et))\\n        if cmp == 0 then\\n            break\\n        elseif cmp < 0 then\\n            -- M0. d < e\\n            rules[#rules + 1] = 0\\n            -- (d, e) \\u2190 (e, d)\\n            dt, et = et, dt\\n            d2, e2 = e2, d2\\n            d3, e3 = e3, d3\\n            ef = mp.approx(et)\\n            ft = mp.carry(mp.sub(dt, et))\\n            ff = -ff\\n        elseif 4 * ff - ef < -1 and d3 == lut3[e3] then\\n            -- M1. e < d \\u2264 5/4 e, d \\u2261 -e (mod 3)\\n            rules[#rules + 1] = 1\\n            -- (d, e) \\u2190 ((2d - e)/3, (2e - d)/3)\\n            dt = mp.third(mp.carryWeak(mp.add(dt, ft)))\\n            et = mp.third(mp.carryWeak(mp.sub(et, ft)))\\n            d2, e2 = e2, d2\\n            d3, e3 = mp.mod3(dt), mp.mod3(et)\\n            ef = mp.approx(et)\\n        elseif 4 * ff - ef < -1 and d2 == e2 and d3 == e3 then\\n            -- M2. e < d \\u2264 5/4 e, d \\u2261 e (mod 6)\\n            rules[#rules + 1] = 2\\n            -- (d, e) \\u2190 ((d - e)/2, e)\\n            dt = mp.half(ft)\\n            d2 = mp.mod2(dt)\\n            d3 = lut3[(d3 - e3) % 3]\\n            ft = mp.carryWeak(mp.sub(dt, et))\\n            ff = mp.approx(ft)\\n        elseif ff - 3 * ef < -1 then\\n            -- M3. d \\u2264 4e\\n            rules[#rules + 1] = 3\\n            -- (d, e) \\u2190 (d - e, e)\\n            dt = mp.carryWeak(ft)\\n            d2 = (d2 - e2) % 2\\n            d3 = (d3 - e3) % 3\\n            ft = mp.carryWeak(mp.sub(dt, et))\\n            ff = mp.approx(ft)\\n        elseif d2 == e2 then\\n            -- M4. d \\u2261 e (mod 2)\\n            rules[#rules + 1] = 2\\n            -- (d, e) \\u2190 ((d - e)/2, e)\\n            dt = mp.half(ft)\\n            d2 = mp.mod2(dt)\\n            d3 = lut3[(d3 - e3) % 3]\\n            ft = mp.carryWeak(mp.sub(dt, et))\\n            ff = mp.approx(ft)\\n        elseif d2 == 0 then\\n            -- M5. d \\u2261 0 (mod 2)\\n            rules[#rules + 1] = 5\\n            -- (d, e) \\u2190 (d/2, e)\\n            dt = mp.half(dt)\\n            d2 = mp.mod2(dt)\\n            d3 = lut3[d3]\\n            ft = mp.carryWeak(mp.sub(dt, et))\\n            ff = mp.approx(ft)\\n        elseif d3 == 0 then\\n            -- M6. d \\u2261 0 (mod 3)\\n            rules[#rules + 1] = 6\\n            -- (d, e) \\u2190 (d/3 - e, e)\\n            dt = mp.carryWeak(mp.sub(mp.third(dt), et))\\n            d2 = (d2 - e2) % 2\\n            d3 = mp.mod3(dt)\\n            ft = mp.carryWeak(mp.sub(dt, et))\\n            ff = mp.approx(ft)\\n        elseif d3 == lut3[e3] then\\n            -- M7. d \\u2261 -e (mod 3)\\n            rules[#rules + 1] = 7\\n            -- (d, e) \\u2190 ((d - 2e)/3, e)\\n            dt = mp.third(mp.carryWeak(mp.sub(ft, et)))\\n            d3 = mp.mod3(dt)\\n            ft = mp.carryWeak(mp.sub(dt, et))\\n            ff = mp.approx(ft)\\n        elseif d3 == e3 then\\n            -- M8. d \\u2261 e (mod 3)\\n            rules[#rules + 1] = 8\\n            -- (d, e) \\u2190 ((d - e)/3, e)\\n            dt = mp.third(ft)\\n            d2 = (d2 - e2) % 2\\n            d3 = mp.mod3(dt)\\n            ft = mp.carryWeak(mp.sub(dt, et))\\n            ff = mp.approx(ft)\\n        else\\n            -- M9. e \\u2261 0 (mod 2)\\n            rules[#rules + 1] = 9\\n            -- (d, e) \\u2190 (d, e/2)\\n            et = mp.half(et)\\n            e2 = mp.mod2(et)\\n            e3 = lut3[e3]\\n            ef = mp.approx(et)\\n            ft = mp.carryWeak(mp.sub(dt, et))\\n            ff = mp.approx(ft)\\n        end\\n    end\\n\\n    local ubits = util.rebaseLE(dt, 2 ^ 24, 2)\\n    while ubits[#ubits] == 0 do ubits[#ubits] = nil end\\n\\n    return {ubits, rules}\\nend\\n\\nreturn {\\n    add = add,\\n    sub = sub,\\n    mul = mul,\\n    encode = encode,\\n    encodeClamped = encodeClamped,\\n    decode = decode,\\n    decodeWide = decodeWide,\\n    decodeClamped = decodeClamped,\\n    eighth = eighth,\\n    bits = bits,\\n    makeRuleset = makeRuleset,\\n}\\n\",\"size\":11778,\"sha256\":\"4161d0fe692ed3c8a82a4d72cce4e9ead7b7405a09c0b51d02d87c6054d7113b\"},\"ccryptolib/internal/hw.lua\":{\"content\":\"local config = require \\\"ccryptolib.config\\\"\\n\\nlocal PERIPHERAL_CHECK_INTERVAL = 10\\n\\nlocal peripheralName = nil\\nlocal peripheralType = nil\\nlocal peripheralSeed = nil\\nlocal lastCheck = -PERIPHERAL_CHECK_INTERVAL\\n\\nlocal function isClassicPeripherals(name)\\n    local methods = peripheral.getMethods(name)\\n    if type(methods) ~= \\\"table\\\" then return false end\\n    local kMethods = {}\\n    for _, method in ipairs(methods) do\\n        kMethods[method] = true\\n    end\\n    if not kMethods.computeSharedSecret then return false end\\n    if not kMethods.deriveEcdhPublicKey then return false end\\n    if not kMethods.derivePublicKey then return false end\\n    if not kMethods.randomBytes then return false end\\n    if not kMethods.sha256 then return false end\\n    if not kMethods.sha512 then return false end\\n    if not kMethods.sign then return false end\\n    if not kMethods.verify then return false end\\n    return true\\nend\\n\\nlocal function findPeripheral()\\n    if peripheralName ~= nil then return end\\n    if os.clock() - lastCheck < PERIPHERAL_CHECK_INTERVAL then return end\\n    lastCheck = os.clock()\\n\\n    if not peripheral then\\n        config.usePeripherals(false)\\n        return\\n    end\\n\\n    local found = peripheral.find(\\\"cryptographic_accelerator\\\", isClassicPeripherals)\\n    if found then\\n        peripheralName = peripheral.getName(found)\\n        peripheralType = \\\"cryptographic_accelerator\\\"\\n        local ok, seed = pcall(peripheral.call, peripheralName, \\\"randomBytes\\\", 32)\\n        if ok then peripheralSeed = seed end\\n    end\\nend\\n\\nlocal function takeSeed()\\n    if not config.usePeripherals() then return end\\n    findPeripheral()\\n    local out = peripheralSeed\\n    peripheralSeed = nil\\n    return out\\nend\\n\\nlocal function checkedCall(method, ...)\\n    if not config.usePeripherals() then return false end\\n    findPeripheral()\\n    if not peripheralName then return false end\\n    if peripheral.hasType(peripheralName, peripheralType) then\\n        local ok, out = pcall(peripheral.call, peripheralName, method, ...)\\n        if not ok or out == nil then return false end\\n        return true, out\\n    else\\n        peripheralName = nil\\n        peripheralType = nil\\n        return false\\n    end\\nend\\n\\nlocal function x25519Exchange(sk, pk)\\n    return checkedCall(\\\"computeSharedSecret\\\", sk, pk)\\nend\\n\\nlocal function x25519PublicKey(sk)\\n    return checkedCall(\\\"deriveEcdhPublicKey\\\", sk)\\nend\\n\\nlocal function ed25519PublicKey(sk)\\n    return checkedCall(\\\"derivePublicKey\\\", sk)\\nend\\n\\nlocal function random(length)\\n    return checkedCall(\\\"randomBytes\\\", length)\\nend\\n\\nlocal function sha256(input)\\n    return checkedCall(\\\"sha256\\\", input, false)\\nend\\n\\nlocal function sha512(input)\\n    return checkedCall(\\\"sha512\\\", input, false)\\nend\\n\\nlocal function ed25519Sign(m, sk)\\n    return checkedCall(\\\"sign\\\", m, sk)\\nend\\n\\nlocal function ed25519Verify(m, s, pk)\\n    return checkedCall(\\\"verify\\\", m, s, pk)\\nend\\n\\nreturn {\\n    takeSeed = takeSeed,\\n    x25519Exchange = x25519Exchange,\\n    x25519PublicKey = x25519PublicKey,\\n    ed25519PublicKey = ed25519PublicKey,\\n    random = random,\\n    sha256 = sha256,\\n    sha512 = sha512,\\n    ed25519Sign = ed25519Sign,\\n    ed25519Verify = ed25519Verify,\\n}\\n\",\"size\":3145,\"sha256\":\"c1420f387520ce054e0fb2e41c45e246daebda3d38c23a75a71fbfdec666b246\"},\"ccryptolib/internal/mp.lua\":{\"content\":\"--- Multi-precision arithmetic on 264-bit integers.\\n\\nlocal unpack = unpack or table.unpack\\n\\n--- A little-endian big integer of width 11 in (-2\\u2075\\u00b2..2\\u2075\\u00b2).\\n--- @class MpSW11L52\\n\\n--- A little-endian big integer of width 11 in (-2\\u00b2\\u2074, 2\\u00b2\\u2074).\\n--- @class MpSW11L24: MpSW11L52\\n\\n--- A little-endian big integer of width 11 in [0..2\\u00b2\\u2074).\\n--- @class MpUW11L24: MpSW11L24\\n\\n--- Carries a number in base 2\\u00b2\\u2074 into a signed limb form.\\n--- @param a MpSW11L52\\n--- @return MpSW11L24 low The carried low limbs.\\n--- @return number carry The overflowed carry.\\nlocal function carryWeak(a)\\n    local a00, a01, a02, a03, a04, a05, a06, a07, a08, a09, a10 = unpack(a)\\n\\n    local h00 = a00 + 3 * 2 ^ 75 - 3 * 2 ^ 75 a01 = a01 + h00 * 2 ^ -24\\n    local h01 = a01 + 3 * 2 ^ 75 - 3 * 2 ^ 75 a02 = a02 + h01 * 2 ^ -24\\n    local h02 = a02 + 3 * 2 ^ 75 - 3 * 2 ^ 75 a03 = a03 + h02 * 2 ^ -24\\n    local h03 = a03 + 3 * 2 ^ 75 - 3 * 2 ^ 75 a04 = a04 + h03 * 2 ^ -24\\n    local h04 = a04 + 3 * 2 ^ 75 - 3 * 2 ^ 75 a05 = a05 + h04 * 2 ^ -24\\n    local h05 = a05 + 3 * 2 ^ 75 - 3 * 2 ^ 75 a06 = a06 + h05 * 2 ^ -24\\n    local h06 = a06 + 3 * 2 ^ 75 - 3 * 2 ^ 75 a07 = a07 + h06 * 2 ^ -24\\n    local h07 = a07 + 3 * 2 ^ 75 - 3 * 2 ^ 75 a08 = a08 + h07 * 2 ^ -24\\n    local h08 = a08 + 3 * 2 ^ 75 - 3 * 2 ^ 75 a09 = a09 + h08 * 2 ^ -24\\n    local h09 = a09 + 3 * 2 ^ 75 - 3 * 2 ^ 75 a10 = a10 + h09 * 2 ^ -24\\n    local h10 = a10 + 3 * 2 ^ 75 - 3 * 2 ^ 75\\n\\n    return {\\n        a00 - h00,\\n        a01 - h01,\\n        a02 - h02,\\n        a03 - h03,\\n        a04 - h04,\\n        a05 - h05,\\n        a06 - h06,\\n        a07 - h07,\\n        a08 - h08,\\n        a09 - h09,\\n        a10 - h10,\\n    }, h10 * 2 ^ -24\\nend\\n\\n--- Carries a number in base 2\\u00b2\\u2074.\\n--- @param a MpSW11L52\\n--- @return MpUW11L24 low The low 11 limbs of the output.\\n--- @return number carry The overflow carry.\\nlocal function carry(a)\\n    local a00, a01, a02, a03, a04, a05, a06, a07, a08, a09, a10 = unpack(a)\\n\\n    local l00 = a00 % 2 ^ 24 a01 = a01 + (a00 - l00) * 2 ^ -24\\n    local l01 = a01 % 2 ^ 24 a02 = a02 + (a01 - l01) * 2 ^ -24\\n    local l02 = a02 % 2 ^ 24 a03 = a03 + (a02 - l02) * 2 ^ -24\\n    local l03 = a03 % 2 ^ 24 a04 = a04 + (a03 - l03) * 2 ^ -24\\n    local l04 = a04 % 2 ^ 24 a05 = a05 + (a04 - l04) * 2 ^ -24\\n    local l05 = a05 % 2 ^ 24 a06 = a06 + (a05 - l05) * 2 ^ -24\\n    local l06 = a06 % 2 ^ 24 a07 = a07 + (a06 - l06) * 2 ^ -24\\n    local l07 = a07 % 2 ^ 24 a08 = a08 + (a07 - l07) * 2 ^ -24\\n    local l08 = a08 % 2 ^ 24 a09 = a09 + (a08 - l08) * 2 ^ -24\\n    local l09 = a09 % 2 ^ 24 a10 = a10 + (a09 - l09) * 2 ^ -24\\n    local l10 = a10 % 2 ^ 24\\n    local h10 = (a10 - l10) * 2 ^ -24\\n\\n    return {l00, l01, l02, l03, l04, l05, l06, l07, l08, l09, l10}, h10\\nend\\n\\n--- Adds two numbers.\\n--- @param a MpSW11L24\\n--- @param b MpSW11L24\\n--- @return MpSW11L52 c a + b\\nlocal function add(a, b)\\n    local a00, a01, a02, a03, a04, a05, a06, a07, a08, a09, a10 = unpack(a)\\n    local b00, b01, b02, b03, b04, b05, b06, b07, b08, b09, b10 = unpack(b)\\n\\n    return {\\n        a00 + b00,\\n        a01 + b01,\\n        a02 + b02,\\n        a03 + b03,\\n        a04 + b04,\\n        a05 + b05,\\n        a06 + b06,\\n        a07 + b07,\\n        a08 + b08,\\n        a09 + b09,\\n        a10 + b10,\\n    }\\nend\\n\\n--- Subtracts a number from another.\\n--- @param a MpSW11L24\\n--- @param b MpSW11L24\\n--- @return MpSW11L52 c a - b\\nlocal function sub(a, b)\\n    local a00, a01, a02, a03, a04, a05, a06, a07, a08, a09, a10 = unpack(a)\\n    local b00, b01, b02, b03, b04, b05, b06, b07, b08, b09, b10 = unpack(b)\\n\\n    return {\\n        a00 - b00,\\n        a01 - b01,\\n        a02 - b02,\\n        a03 - b03,\\n        a04 - b04,\\n        a05 - b05,\\n        a06 - b06,\\n        a07 - b07,\\n        a08 - b08,\\n        a09 - b09,\\n        a10 - b10,\\n    }\\nend\\n\\n--- Computes the lower half of a product between two numbers.\\n--- @param a MpUW11L24\\n--- @param b MpUW11L24\\n--- @return MpUW11L24 c a \\u00d7 b (mod 2\\u00b2\\u2076\\u2074)\\n--- @return number carry \\u230aa \\u00d7 b \\u00f7 2\\u00b2\\u2076\\u2074\\u230b\\nlocal function lmul(a, b)\\n    local a00, a01, a02, a03, a04, a05, a06, a07, a08, a09, a10 = unpack(a)\\n    local b00, b01, b02, b03, b04, b05, b06, b07, b08, b09, b10 = unpack(b)\\n\\n    return carry {\\n        a00 * b00,\\n        a01 * b00 + a00 * b01,\\n        a02 * b00 + a01 * b01 + a00 * b02,\\n        a03 * b00 + a02 * b01 + a01 * b02 + a00 * b03,\\n        a04 * b00 + a03 * b01 + a02 * b02 + a01 * b03 + a00 * b04,\\n        a05 * b00 + a04 * b01 + a03 * b02 + a02 * b03 + a01 * b04 + a00 * b05,\\n        a06 * b00 + a05 * b01 + a04 * b02 + a03 * b03 + a02 * b04 + a01 * b05 + a00 * b06,\\n        a07 * b00 + a06 * b01 + a05 * b02 + a04 * b03 + a03 * b04 + a02 * b05 + a01 * b06 + a00 * b07,\\n        a08 * b00 + a07 * b01 + a06 * b02 + a05 * b03 + a04 * b04 + a03 * b05 + a02 * b06 + a01 * b07 + a00 * b08,\\n        a09 * b00 + a08 * b01 + a07 * b02 + a06 * b03 + a05 * b04 + a04 * b05 + a03 * b06 + a02 * b07 + a01 * b08 + a00 * b09,\\n        a10 * b00 + a09 * b01 + a08 * b02 + a07 * b03 + a06 * b04 + a05 * b05 + a04 * b06 + a03 * b07 + a02 * b08 + a01 * b09 + a00 * b10,\\n    }\\nend\\n\\n--- Computes the a product between two numbers.\\n--- @param a MpUW11L24\\n--- @param b MpUW11L24\\n--- @return MpUW11L24 low The low 11 limbs of a \\u00d7 b.\\n--- @return MpUW11L24 high The high 11 limbs of a \\u00d7 b.\\nlocal function mul(a, b)\\n    local low, of = lmul(a, b)\\n\\n    local _, a01, a02, a03, a04, a05, a06, a07, a08, a09, a10 = unpack(a)\\n    local _, b01, b02, b03, b04, b05, b06, b07, b08, b09, b10 = unpack(b)\\n\\n    -- The carry is always 0.\\n    return low, (carry {\\n        of + a10 * b01 + a09 * b02 + a08 * b03 + a07 * b04 + a06 * b05 + a05 * b06 + a04 * b07 + a03 * b08 + a02 * b09 + a01 * b10,\\n        a10 * b02 + a09 * b03 + a08 * b04 + a07 * b05 + a06 * b06 + a05 * b07 + a04 * b08 + a03 * b09 + a02 * b10,\\n        a10 * b03 + a09 * b04 + a08 * b05 + a07 * b06 + a06 * b07 + a05 * b08 + a04 * b09 + a03 * b10,\\n        a10 * b04 + a09 * b05 + a08 * b06 + a07 * b07 + a06 * b08 + a05 * b09 + a04 * b10,\\n        a10 * b05 + a09 * b06 + a08 * b07 + a07 * b08 + a06 * b09 + a05 * b10,\\n        a10 * b06 + a09 * b07 + a08 * b08 + a07 * b09 + a06 * b10,\\n        a10 * b07 + a09 * b08 + a08 * b09 + a07 * b10,\\n        a10 * b08 + a09 * b09 + a08 * b10,\\n        a10 * b09 + a09 * b10,\\n        a10 * b10,\\n        0\\n    })\\nend\\n\\n--- Computes a double-width sum of two numbers.\\n--- @param a0 MpUW11L24 The low 11 limbs of a.\\n--- @param a1 MpUW11L24 The high 11 limbs of a.\\n--- @param b0 MpUW11L24 The low 11 limbs of b.\\n--- @param b1 MpUW11L24 The high 11 limbs of b.\\n--- @return MpUW11L24 c0 The low 11 limbs of a + b.\\n--- @return MpUW11L24 c1 The high 11 limbs of a + b.\\n--- @return number The carry.\\nlocal function dwadd(a0, a1, b0, b1)\\n    local low, c = carry(add(a0, b0))\\n    local high = add(a1, b1)\\n    high[1] = high[1] + c\\n    return low, carry(high)\\nend\\n\\n--- Computes half of a number.\\n--- @param a MpSW11L24 The number to halve, must be even.\\n--- @return MpSW11L24 c a \\u00f7 2\\nlocal function half(a)\\n    local a00, a01, a02, a03, a04, a05, a06, a07, a08, a09, a10 = unpack(a)\\n\\n    return (carryWeak {\\n        a00 * 0.5 + a01 * 2 ^ 23,\\n        a02 * 2 ^ 23,\\n        a03 * 2 ^ 23,\\n        a04 * 2 ^ 23,\\n        a05 * 2 ^ 23,\\n        a06 * 2 ^ 23,\\n        a07 * 2 ^ 23,\\n        a08 * 2 ^ 23,\\n        a09 * 2 ^ 23,\\n        a10 * 2 ^ 23,\\n        0,\\n    })\\nend\\n\\n--- Computes a third of a number.\\n--- @param a MpSW11L24 The number to divide, must be a multiple of 3.\\n--- @return MpSW11L24 c a \\u00f7 3\\nlocal function third(a)\\n    local a00, a01, a02, a03, a04, a05, a06, a07, a08, a09, a10 = unpack(a)\\n\\n    local d00 = a00 * 0xaaaaaa\\n    local d01 = a01 * 0xaaaaaa + d00\\n    local d02 = a02 * 0xaaaaaa + d01\\n    local d03 = a03 * 0xaaaaaa + d02\\n    local d04 = a04 * 0xaaaaaa + d03\\n    local d05 = a05 * 0xaaaaaa + d04\\n    local d06 = a06 * 0xaaaaaa + d05\\n    local d07 = a07 * 0xaaaaaa + d06\\n    local d08 = a08 * 0xaaaaaa + d07\\n    local d09 = a09 * 0xaaaaaa + d08\\n    local d10 = a10 * 0xaaaaaa + d09\\n\\n    -- We compute the modular division mod 2\\u00b2\\u2076\\u2074. The carry isn't 0 but it isn't\\n    -- part of a \\u00f7 3 either.\\n    return (carryWeak {\\n        a00 + d00,\\n        a01 + d01,\\n        a02 + d02,\\n        a03 + d03,\\n        a04 + d04,\\n        a05 + d05,\\n        a06 + d06,\\n        a07 + d07,\\n        a08 + d08,\\n        a09 + d09,\\n        a10 + d10,\\n    })\\nend\\n\\n--- Computes a number modulo 2.\\n--- @param a MpSW11L24\\n--- @return number c a mod 2.\\nlocal function mod2(a)\\n    return a[1] % 2\\nend\\n\\n--- Computes a number modulo 3.\\n--- @param a MpSW11L24\\n--- @return number c a mod 3.\\nlocal function mod3(a)\\n    local a00, a01, a02, a03, a04, a05, a06, a07, a08, a09, a10 = unpack(a)\\n    return (a00 + a01 + a02 + a03 + a04 + a05 + a06 + a07 + a08 + a09 + a10) % 3\\nend\\n\\n--- Computes a double representing the most-significant bits of a number.\\n--- @param a MpSW11L52\\n--- @return number c A floating-point approximation for the value of a.\\nlocal function approx(a)\\n    local a00, a01, a02, a03, a04, a05, a06, a07, a08, a09, a10 = unpack(a)\\n    return a00\\n        + a01 * 2 ^ 24\\n        + a02 * 2 ^ 48\\n        + a03 * 2 ^ 72\\n        + a04 * 2 ^ 96\\n        + a05 * 2 ^ 120\\n        + a06 * 2 ^ 144\\n        + a07 * 2 ^ 168\\n        + a08 * 2 ^ 192\\n        + a09 * 2 ^ 216\\n        + a10 * 2 ^ 240\\nend\\n\\n--- @param a MpUW11L24\\n--- @param b MpUW11L24\\n--- @return number c A number that compares to 0 the same as a compares to b.\\nlocal function cmp(a, b)\\n    return a[11] < b[11] and -1\\n        or a[11] > b[11] and 1\\n        or a[10] < b[10] and -1\\n        or a[10] > b[10] and 1\\n        or a[9] < b[9] and -1\\n        or a[9] > b[9] and 1\\n        or a[8] < b[8] and -1\\n        or a[8] > b[8] and 1\\n        or a[7] < b[7] and -1\\n        or a[7] > b[7] and 1\\n        or a[6] < b[6] and -1\\n        or a[6] > b[6] and 1\\n        or a[5] < b[5] and -1\\n        or a[5] > b[5] and 1\\n        or a[4] < b[4] and -1\\n        or a[4] > b[4] and 1\\n        or a[3] < b[3] and -1\\n        or a[3] > b[3] and 1\\n        or a[2] < b[2] and -1\\n        or a[2] > b[2] and 1\\n        or a[1] < b[1] and -1\\n        or a[1] > b[1] and 1\\n        or 0\\nend\\n\\nlocal function num(a)\\n    return {a, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}\\nend\\n\\nreturn {\\n    carry = carry,\\n    carryWeak = carryWeak,\\n    add = add,\\n    sub = sub,\\n    dwadd = dwadd,\\n    lmul = lmul,\\n    mul = mul,\\n    half = half,\\n    third = third,\\n    mod2 = mod2,\\n    mod3 = mod3,\\n    approx = approx,\\n    cmp = cmp,\\n    num = num,\\n}\\n\",\"size\":10367,\"sha256\":\"d6b955cdc6edbc7dcce667b612492f4e3620fee424ce4b8681fa3daa997a1074\"},\"ccryptolib/internal/packing.lua\":{\"content\":\"--- High-performance binary packing of integers.\\n---\\n--- Remark (and warning):\\n--- For performance reasons, **the generated functions do not check types,\\n--- lengths, nor ranges**. You must ensure that the passed arguments are\\n--- well-formed and respect the format string yourself.\\n\\nlocal fmt = string.format\\n\\nlocal function mkPack(words, BE)\\n    local out = \\\"local C=string.char return function(_,\\\"\\n    local nb = 0\\n    for i = 1, #words do\\n        out = out .. fmt(\\\"n%d,\\\", i)\\n        nb = nb + words[i]\\n    end\\n    out = out:sub(1, -2) .. \\\")local \\\"\\n    for i = 1, nb do\\n        out = out .. fmt(\\\"b%d,\\\", i)\\n    end\\n    out = out:sub(1, -2) .. \\\" \\\"\\n    local bi = 1\\n    for i = 1, #words do\\n        for _ = 1, words[i] - 1 do\\n            out = out .. fmt(\\\"b%d=n%d%%2^8 n%d=(n%d-b%d)*2^-8 \\\", bi, i, i, i, bi)\\n            bi = bi + 1\\n        end\\n        bi = bi + 1\\n    end\\n    out = out .. \\\"return C(\\\"\\n    bi = 1\\n    if not BE then\\n        for i = 1, #words do\\n            for _ = 1, words[i] - 1 do\\n                out = out .. fmt(\\\"b%d,\\\", bi)\\n                bi = bi + 1\\n            end\\n            out = out .. fmt(\\\"n%d%%2^8,\\\", i)\\n            bi = bi + 1\\n        end\\n    else\\n        for i = 1, #words do\\n            out = out .. fmt(\\\"n%d%%2^8,\\\", i)\\n            bi = bi + words[i] - 2\\n            for _ = 1, words[i] - 1 do\\n                out = out .. fmt(\\\"b%d,\\\", bi)\\n                bi = bi - 1\\n            end\\n            bi = bi + words[i] + 1\\n        end\\n    end\\n    out = out:sub(1, -2) .. \\\")end\\\"\\n    return load(out)()\\nend\\n\\nlocal function mkUnpack(words, BE)\\n    local out = \\\"local B=string.byte return function(_,s,i)local \\\"\\n    local bi = 1\\n    if not BE then\\n        for i = 1, #words do\\n            for _ = 1, words[i] do\\n                out = out .. fmt(\\\"b%d,\\\", bi)\\n                bi = bi + 1\\n            end\\n        end\\n    else\\n        for i = 1, #words do\\n            bi = bi + words[i] - 1\\n            for _ = 1, words[i] do\\n                out = out .. fmt(\\\"b%d,\\\", bi)\\n                bi = bi - 1\\n            end\\n            bi = bi + words[i] + 1\\n        end\\n    end\\n    out = out:sub(1, -2) .. fmt(\\\"=B(s,i,i+%d)return \\\", bi - 2)\\n    bi = 1\\n    for i = 1, #words do\\n        out = out .. fmt(\\\"b%d\\\", bi)\\n        bi = bi + 1\\n        for j = 2, words[i] do\\n            out = out .. fmt(\\\"+b%d*2^%d\\\", bi, 8 * j - 8)\\n            bi = bi + 1\\n        end\\n        out = out .. \\\",\\\"\\n    end\\n    out = out .. fmt(\\\"i+%d end\\\", bi - 1)\\n    return load(out)()\\nend\\n\\n-- Check whether string.pack is implemented in a high-speed language.\\nif not string.pack or pcall(string.dump, string.pack) then\\n    local function compile(fmt, fn)\\n        local e = assert(fmt:match(\\\"^([><])I[I%d]+$\\\"), \\\"invalid format string\\\")\\n        local w = {}\\n        for i in fmt:gmatch(\\\"I([%d]+)\\\") do\\n            local n = tonumber(i) or 4\\n            assert(n > 0 and n <= 16, \\\"integral size out of limits\\\")\\n            w[#w + 1] = n\\n        end\\n        return fn(w, e == \\\">\\\")\\n    end\\n\\n    local packCache = {}\\n    local unpackCache = {}\\n\\n    -- I CAN'T EVEN WITH THIS EXTENSION, WHY CAN'T IT HANDLE MORE THAN A SINGLE\\n    -- LINE OF RETURN DESCRIPTION? LOOK AT IT!!! THE COMMENT GOES OVER THERE ------------------------------------------------------------------> look! \\u2193 \\u2193 \\u2193\\n\\n    --- (string.pack is nil) Compiles a binary packing function.\\n    ---\\n    --- Errors if the format string is invalid or has an invalid integral size,\\n    --- or if the compiled function turns out too large.\\n    ---\\n    --- @param fmt string A string matched by `^([><])I[I%d]+$`.\\n    --- @return fun(_ignored: any, ...: any): string pack A function that behaves like an unsafe version of `string.pack` for the given format string.\\n    --- @return string fmt\\n    local function compilePack(fmt)\\n        if not packCache[fmt] then\\n            packCache[fmt] = compile(fmt, mkPack)\\n        end\\n        return packCache[fmt], fmt\\n    end\\n\\n    --- (string.pack is nil) Compiles a binary unpacking function.\\n    ---\\n    --- Errors if the format string is invalid or has an invalid integral size,\\n    --- or if the compiled function turns out too large.\\n    ---\\n    --- @param fmt string A string matched by `^([><])I[I%d]+$`.\\n    --- @return fun(_ignored: any, str: string, pos: number) unpack A function that behaves like an unsafe version of `string.unpack` for the given format string. Note that the third argument isn't optional.\\n    --- @return string fmt\\n    local function compileUnpack(fmt)\\n        if not unpackCache[fmt] then\\n            unpackCache[fmt] = compile(fmt, mkUnpack)\\n        end\\n        return unpackCache[fmt], fmt\\n    end\\n\\n    return {\\n        compilePack = compilePack,\\n        compileUnpack = compileUnpack,\\n    }\\nelse\\n    --- (string.pack isn't nil) It's string.pack! It returns string.pack!\\n    --- @param fmt string\\n    --- @return fun(fmt: string, ...: any): string pack string.pack!\\n    --- @return string fmt\\n    local function compilePack(fmt) return string.pack, fmt end\\n\\n    --- (string.pack isn't nil) It's string.unpack! It returns string.unpack!\\n    --- @param fmt string\\n    --- @return fun(fmt: string, str: string, pos: number) unpack string.unpack!\\n    --- @return string fmt\\n    local function compileUnpack(fmt) return string.unpack, fmt end\\n\\n    return {\\n        compilePack = compilePack,\\n        compileUnpack = compileUnpack,\\n    }\\nend\\n\",\"size\":5343,\"sha256\":\"12ceb7323c616289071a3d5e255cf0dade7a7532580155f91b7ac3047bb59476\"},\"ccryptolib/internal/sha512.lua\":{\"content\":\"--- The SHA512 cryptographic hash function.\\n\\nlocal expect = require \\\"cc.expect\\\".expect\\nlocal packing = require \\\"ccryptolib.internal.packing\\\"\\nlocal hw = require \\\"ccryptolib.internal.hw\\\"\\n\\nlocal shl = bit32.lshift\\nlocal shr = bit32.rshift\\nlocal bxor = bit32.bxor\\nlocal bnot = bit32.bnot\\nlocal band = bit32.band\\nlocal p1x16, fmt1x16 = packing.compilePack(\\\">I16\\\")\\nlocal p16x4, fmt16x4 = packing.compilePack(\\\">I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4\\\")\\nlocal u32x4, fmt32x4 = packing.compileUnpack(\\\">I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4\\\")\\n\\nlocal function carry64(a1, a0)\\n    local r0 = a0 % 2 ^ 32\\n    a1 = a1 + (a0 - r0) / 2 ^ 32\\n    return a1 % 2 ^ 32, r0\\nend\\n\\nlocal K = {\\n    0x428a2f98, 0xd728ae22, 0x71374491, 0x23ef65cd, 0xb5c0fbcf, 0xec4d3b2f,\\n    0xe9b5dba5, 0x8189dbbc, 0x3956c25b, 0xf348b538, 0x59f111f1, 0xb605d019,\\n    0x923f82a4, 0xaf194f9b, 0xab1c5ed5, 0xda6d8118, 0xd807aa98, 0xa3030242,\\n    0x12835b01, 0x45706fbe, 0x243185be, 0x4ee4b28c, 0x550c7dc3, 0xd5ffb4e2,\\n    0x72be5d74, 0xf27b896f, 0x80deb1fe, 0x3b1696b1, 0x9bdc06a7, 0x25c71235,\\n    0xc19bf174, 0xcf692694, 0xe49b69c1, 0x9ef14ad2, 0xefbe4786, 0x384f25e3,\\n    0x0fc19dc6, 0x8b8cd5b5, 0x240ca1cc, 0x77ac9c65, 0x2de92c6f, 0x592b0275,\\n    0x4a7484aa, 0x6ea6e483, 0x5cb0a9dc, 0xbd41fbd4, 0x76f988da, 0x831153b5,\\n    0x983e5152, 0xee66dfab, 0xa831c66d, 0x2db43210, 0xb00327c8, 0x98fb213f,\\n    0xbf597fc7, 0xbeef0ee4, 0xc6e00bf3, 0x3da88fc2, 0xd5a79147, 0x930aa725,\\n    0x06ca6351, 0xe003826f, 0x14292967, 0x0a0e6e70, 0x27b70a85, 0x46d22ffc,\\n    0x2e1b2138, 0x5c26c926, 0x4d2c6dfc, 0x5ac42aed, 0x53380d13, 0x9d95b3df,\\n    0x650a7354, 0x8baf63de, 0x766a0abb, 0x3c77b2a8, 0x81c2c92e, 0x47edaee6,\\n    0x92722c85, 0x1482353b, 0xa2bfe8a1, 0x4cf10364, 0xa81a664b, 0xbc423001,\\n    0xc24b8b70, 0xd0f89791, 0xc76c51a3, 0x0654be30, 0xd192e819, 0xd6ef5218,\\n    0xd6990624, 0x5565a910, 0xf40e3585, 0x5771202a, 0x106aa070, 0x32bbd1b8,\\n    0x19a4c116, 0xb8d2d0c8, 0x1e376c08, 0x5141ab53, 0x2748774c, 0xdf8eeb99,\\n    0x34b0bcb5, 0xe19b48a8, 0x391c0cb3, 0xc5c95a63, 0x4ed8aa4a, 0xe3418acb,\\n    0x5b9cca4f, 0x7763e373, 0x682e6ff3, 0xd6b2b8a3, 0x748f82ee, 0x5defb2fc,\\n    0x78a5636f, 0x43172f60, 0x84c87814, 0xa1f0ab72, 0x8cc70208, 0x1a6439ec,\\n    0x90befffa, 0x23631e28, 0xa4506ceb, 0xde82bde9, 0xbef9a3f7, 0xb2c67915,\\n    0xc67178f2, 0xe372532b, 0xca273ece, 0xea26619c, 0xd186b8c7, 0x21c0c207,\\n    0xeada7dd6, 0xcde0eb1e, 0xf57d4f7f, 0xee6ed178, 0x06f067aa, 0x72176fba,\\n    0x0a637dc5, 0xa2c898a6, 0x113f9804, 0xbef90dae, 0x1b710b35, 0x131c471b,\\n    0x28db77f5, 0x23047d84, 0x32caab7b, 0x40c72493, 0x3c9ebe0a, 0x15c9bebc,\\n    0x431d67c4, 0x9c100d4c, 0x4cc5d4be, 0xcb3e42b6, 0x597f299c, 0xfc657e2a,\\n    0x5fcb6fab, 0x3ad6faec, 0x6c44198c, 0x4a475817,\\n}\\n\\n--- Hashes data bytes using SHA512.\\n--- @param data string The input data.\\n--- @return string hash The 64-byte hash value.\\nlocal function digest(data)\\n    expect(1, data, \\\"string\\\")\\n    local ok, out = hw.sha512(data)\\n    if ok then return out end\\n\\n    -- Pad input.\\n    local bitlen = #data * 8\\n    local padlen = -(#data + 17) % 128\\n    data = data .. \\\"\\\\x80\\\" .. (\\\"\\\\0\\\"):rep(padlen) .. p1x16(fmt1x16, bitlen)\\n\\n    -- Initialize state.\\n    local h01, h00 = 0x6a09e667, 0xf3bcc908\\n    local h11, h10 = 0xbb67ae85, 0x84caa73b\\n    local h21, h20 = 0x3c6ef372, 0xfe94f82b\\n    local h31, h30 = 0xa54ff53a, 0x5f1d36f1\\n    local h41, h40 = 0x510e527f, 0xade682d1\\n    local h51, h50 = 0x9b05688c, 0x2b3e6c1f\\n    local h61, h60 = 0x1f83d9ab, 0xfb41bd6b\\n    local h71, h70 = 0x5be0cd19, 0x137e2179\\n\\n    -- Digest.\\n    for i = 1, #data, 128 do\\n        local w = {u32x4(fmt32x4, data, i)}\\n\\n        -- Message schedule.\\n        for j = 33, 160, 2 do\\n            local wf1, wf0 = w[j - 30], w[j - 29]\\n            local t1 = shr(wf1, 1) + shl(wf0, 31)\\n            local t0 = shr(wf0, 1) + shl(wf1, 31)\\n            local u1 = shr(wf1, 8) + shl(wf0, 24)\\n            local u0 = shr(wf0, 8) + shl(wf1, 24)\\n            local v1 = shr(wf1, 7)\\n            local v0 = shr(wf0, 7) + shl(wf1, 25)\\n\\n            local w21, w20 = w[j - 4], w[j - 3]\\n            local w1 = shr(w21, 19) + shl(w20, 13)\\n            local w0 = shr(w20, 19) + shl(w21, 13)\\n            local x0 = shr(w21, 29) + shl(w20, 3)\\n            local x1 = shr(w20, 29) + shl(w21, 3)\\n            local y1 = shr(w21, 6)\\n            local y0 = shr(w20, 6) + shl(w21, 26)\\n\\n            local r1, r0 =\\n                w[j - 32] + bxor(t1, u1, v1) + w[j - 14] + bxor(w1, x1, y1),\\n                w[j - 31] + bxor(t0, u0, v0) + w[j - 13] + bxor(w0, x0, y0)\\n\\n            w[j], w[j + 1] = carry64(r1, r0)\\n        end\\n\\n        -- Block function.\\n        local a1, a0 = h01, h00\\n        local b1, b0 = h11, h10\\n        local c1, c0 = h21, h20\\n        local d1, d0 = h31, h30\\n        local e1, e0 = h41, h40\\n        local f1, f0 = h51, h50\\n        local g1, g0 = h61, h60\\n        local h1, h0 = h71, h70\\n        for j = 1, 160, 2 do\\n            local t1 = shr(e1, 14) + shl(e0, 18)\\n            local t0 = shr(e0, 14) + shl(e1, 18)\\n            local u1 = shr(e1, 18) + shl(e0, 14)\\n            local u0 = shr(e0, 18) + shl(e1, 14)\\n            local v0 = shr(e1, 9) + shl(e0, 23)\\n            local v1 = shr(e0, 9) + shl(e1, 23)\\n            local s11 = bxor(t1, u1, v1)\\n            local s10 = bxor(t0, u0, v0)\\n\\n            local ch1 = bxor(band(e1, f1), band(bnot(e1), g1))\\n            local ch0 = bxor(band(e0, f0), band(bnot(e0), g0))\\n\\n            local temp11 = h1 + s11 + ch1 + K[j] + w[j]\\n            local temp10 = h0 + s10 + ch0 + K[j + 1] + w[j + 1]\\n\\n            local w1 = shr(a1, 28) + shl(a0, 4)\\n            local w0 = shr(a0, 28) + shl(a1, 4)\\n            local x0 = shr(a1, 2) + shl(a0, 30)\\n            local x1 = shr(a0, 2) + shl(a1, 30)\\n            local y0 = shr(a1, 7) + shl(a0, 25)\\n            local y1 = shr(a0, 7) + shl(a1, 25)\\n            local s01 = bxor(w1, x1, y1)\\n            local s00 = bxor(w0, x0, y0)\\n\\n            local maj1 = bxor(band(a1, b1), band(a1, c1), band(b1, c1))\\n            local maj0 = bxor(band(a0, b0), band(a0, c0), band(b0, c0))\\n\\n            local temp21 = s01 + maj1\\n            local temp20 = s00 + maj0\\n\\n            h1 = g1  h0 = g0\\n            g1 = f1  g0 = f0\\n            f1 = e1  f0 = e0\\n            e1, e0 = carry64(d1 + temp11, d0 + temp10)\\n            d1 = c1  d0 = c0\\n            c1 = b1  c0 = b0\\n            b1 = a1  b0 = a0\\n            a1, a0 = carry64(temp11 + temp21, temp10 + temp20)\\n        end\\n\\n        h01, h00 = carry64(h01 + a1, h00 + a0)\\n        h11, h10 = carry64(h11 + b1, h10 + b0)\\n        h21, h20 = carry64(h21 + c1, h20 + c0)\\n        h31, h30 = carry64(h31 + d1, h30 + d0)\\n        h41, h40 = carry64(h41 + e1, h40 + e0)\\n        h51, h50 = carry64(h51 + f1, h50 + f0)\\n        h61, h60 = carry64(h61 + g1, h60 + g0)\\n        h71, h70 = carry64(h71 + h1, h70 + h0)\\n    end\\n\\n    return p16x4(fmt16x4,\\n        h01, h00, h11, h10, h21, h20, h31, h30,\\n        h41, h40, h51, h50, h61, h60, h71, h70\\n    )\\nend\\n\\nreturn {\\n    digest = digest,\\n}\\n\",\"size\":6948,\"sha256\":\"989affb1c13828a299195574faa56f3a5d6498c65c739ead7da5d8c50e4e7e37\"},\"ccryptolib/internal/util.lua\":{\"content\":\"local function lassert(val, err, level)\\n    if not val then error(err, level + 1) end\\n    return val\\nend\\n\\n--- Converts a little-endian array from one power-of-two base to another.\\n--- @param a number[] The array to convert, in little-endian.\\n--- @param base1 number The base to convert from. Must be a power of 2.\\n--- @param base2 number The base to convert to. Must be a power of 2.\\n--- @return number[]\\nlocal function rebaseLE(a, base1, base2) -- TODO Write contract properly.\\n    local out = {}\\n    local outlen = 1\\n    local acc = 0\\n    local mul = 1\\n    for i = 1, #a do\\n        acc = acc + a[i] * mul\\n        mul = mul * base1\\n        while mul >= base2 do\\n            local rem = acc % base2\\n            acc = (acc - rem) / base2\\n            mul = mul / base2\\n            out[outlen] = rem\\n            outlen = outlen + 1\\n        end\\n    end\\n    if mul > 0 then\\n        out[outlen] = acc\\n    end\\n    return out\\nend\\n\\n--- Decodes bits with X25519/Ed25519 exponent clamping.\\n--- @param str string The 32-byte encoded exponent.\\n--- @return number[] bits The decoded clamped bits.\\nlocal function bits(str)\\n    -- Decode.\\n    local bytes = {str:byte(1, 32)}\\n    local out = {}\\n    for i = 1, 32 do\\n        local byte = bytes[i]\\n        for j = -7, 0 do\\n            local bit = byte % 2\\n            out[8 * i + j] = bit\\n            byte = (byte - bit) / 2\\n        end\\n    end\\n\\n    -- Clamp.\\n    out[1] = 0\\n    out[2] = 0\\n    out[3] = 0\\n    out[255] = 1\\n    out[256] = 0\\n\\n    return out\\nend\\n\\n--- Decodes bits with X25519/Ed25519 exponent clamping and division by 8.\\n--- @param str string The 32-byte encoded exponent.\\n--- @return number[] bits The decoded clamped bits, divided by 8.\\nlocal function bits8(str)\\n    return {unpack(bits(str), 4)}\\nend\\n\\nreturn {\\n    lassert = lassert,\\n    rebaseLE = rebaseLE,\\n    bits = bits,\\n    bits8 = bits8,\\n}\\n\",\"size\":1843,\"sha256\":\"711be5e43900fc3af46c61e812bd64c48abcc6b78d698eb9fb82f0285eef6844\"},\"ccryptolib/poly1305.lua\":{\"content\":\"--- The Poly1305 one-time authenticator.\\n\\nlocal expect  = require \\\"cc.expect\\\".expect\\nlocal lassert = require \\\"ccryptolib.internal.util\\\".lassert\\nlocal packing = require \\\"ccryptolib.internal.packing\\\"\\n\\nlocal u4x4, fmt4x4 = packing.compileUnpack(\\\"<I4I4I4I4\\\")\\nlocal p4x4 = packing.compilePack(fmt4x4)\\n\\n--- Computes a Poly1305 message authentication code.\\n--- @param key string A 32-byte single-use random key.\\n--- @param message string The message to authenticate.\\n--- @return string tag The 16-byte authentication tag.\\nlocal function mac(key, message)\\n    expect(1, key, \\\"string\\\")\\n    lassert(#key == 32, \\\"key length must be 32\\\", 2)\\n    expect(2, message, \\\"string\\\")\\n\\n    -- Pad message.\\n    local pbplen = #message - 15\\n    if #message % 16 ~= 0 or #message == 0 then\\n        message = message .. \\\"\\\\1\\\"\\n        message = message .. (\\\"\\\\0\\\"):rep(-#message % 16)\\n    end\\n\\n    -- Decode r.\\n    local R0, R1, R2, R3 = u4x4(fmt4x4, key, 1)\\n\\n    -- Clamp and shift.\\n    R0 = R0 % 2 ^ 28\\n    R1 = (R1 - R1 % 4) % 2 ^ 28 * 2 ^ 32\\n    R2 = (R2 - R2 % 4) % 2 ^ 28 * 2 ^ 64\\n    R3 = (R3 - R3 % 4) % 2 ^ 28 * 2 ^ 96\\n\\n    -- Split.\\n    local r0 = R0 % 2 ^ 18   local r1 = R0 - r0\\n    local r2 = R1 % 2 ^ 50   local r3 = R1 - r2\\n    local r4 = R2 % 2 ^ 82   local r5 = R2 - r4\\n    local r6 = R3 % 2 ^ 112  local r7 = R3 - r6\\n\\n    -- Generate scaled key.\\n    local S1 = 5 / 2 ^ 130 * R1\\n    local S2 = 5 / 2 ^ 130 * R2\\n    local S3 = 5 / 2 ^ 130 * R3\\n\\n    -- Split.\\n    local s2 = S1 % 2 ^ -80  local s3 = S1 - s2\\n    local s4 = S2 % 2 ^ -48  local s5 = S2 - s4\\n    local s6 = S3 % 2 ^ -16  local s7 = S3 - s6\\n\\n    local h0, h1, h2, h3, h4, h5, h6, h7 = 0, 0, 0, 0, 0, 0, 0, 0\\n\\n    for i = 1, #message, 16 do\\n        -- Decode message block.\\n        local m0, m1, m2, m3 = u4x4(fmt4x4, message, i)\\n\\n        -- Shift message and add.\\n        local x0 = h0 + h1 + m0\\n        local x2 = h2 + h3 + m1 * 2 ^ 32\\n        local x4 = h4 + h5 + m2 * 2 ^ 64\\n        local x6 = h6 + h7 + m3 * 2 ^ 96\\n\\n        -- Apply per-block padding when applicable.\\n        if i <= pbplen then x6 = x6 + 2 ^ 128 end\\n\\n        -- Multiply\\n        h0 = x0 * r0 + x2 * s6 + x4 * s4 + x6 * s2\\n        h1 = x0 * r1 + x2 * s7 + x4 * s5 + x6 * s3\\n        h2 = x0 * r2 + x2 * r0 + x4 * s6 + x6 * s4\\n        h3 = x0 * r3 + x2 * r1 + x4 * s7 + x6 * s5\\n        h4 = x0 * r4 + x2 * r2 + x4 * r0 + x6 * s6\\n        h5 = x0 * r5 + x2 * r3 + x4 * r1 + x6 * s7\\n        h6 = x0 * r6 + x2 * r4 + x4 * r2 + x6 * r0\\n        h7 = x0 * r7 + x2 * r5 + x4 * r3 + x6 * r1\\n\\n        -- Carry.\\n        local y0 = h0 + 3 * 2 ^ 69  - 3 * 2 ^ 69   h0 = h0 - y0  h1 = h1 + y0\\n        local y1 = h1 + 3 * 2 ^ 83  - 3 * 2 ^ 83   h1 = h1 - y1  h2 = h2 + y1\\n        local y2 = h2 + 3 * 2 ^ 101 - 3 * 2 ^ 101  h2 = h2 - y2  h3 = h3 + y2\\n        local y3 = h3 + 3 * 2 ^ 115 - 3 * 2 ^ 115  h3 = h3 - y3  h4 = h4 + y3\\n        local y4 = h4 + 3 * 2 ^ 133 - 3 * 2 ^ 133  h4 = h4 - y4  h5 = h5 + y4\\n        local y5 = h5 + 3 * 2 ^ 147 - 3 * 2 ^ 147  h5 = h5 - y5  h6 = h6 + y5\\n        local y6 = h6 + 3 * 2 ^ 163 - 3 * 2 ^ 163  h6 = h6 - y6  h7 = h7 + y6\\n        local y7 = h7 + 3 * 2 ^ 181 - 3 * 2 ^ 181  h7 = h7 - y7\\n\\n        -- Reduce carry overflow into first limb.\\n        h0 = h0 + 5 / 2 ^ 130 * y7\\n    end\\n\\n    -- Carry canonically.\\n    local c0 = h0 % 2 ^ 16   h1 = h0 - c0 + h1\\n    local c1 = h1 % 2 ^ 32   h2 = h1 - c1 + h2\\n    local c2 = h2 % 2 ^ 48   h3 = h2 - c2 + h3\\n    local c3 = h3 % 2 ^ 64   h4 = h3 - c3 + h4\\n    local c4 = h4 % 2 ^ 80   h5 = h4 - c4 + h5\\n    local c5 = h5 % 2 ^ 96   h6 = h5 - c5 + h6\\n    local c6 = h6 % 2 ^ 112  h7 = h6 - c6 + h7\\n    local c7 = h7 % 2 ^ 130\\n\\n    -- Reduce carry overflow.\\n    h0 = c0 + 5 / 2 ^ 130 * (h7 - c7)\\n    c0 = h0 % 2 ^ 16\\n    c1 = h0 - c0 + c1\\n\\n    -- Canonicalize.\\n    if      c7 == 0x3ffff * 2 ^ 112\\n        and c6 == 0xffff * 2 ^ 96\\n        and c5 == 0xffff * 2 ^ 80\\n        and c4 == 0xffff * 2 ^ 64\\n        and c3 == 0xffff * 2 ^ 48\\n        and c2 == 0xffff * 2 ^ 32\\n        and c1 == 0xffff * 2 ^ 16\\n        and c0 >= 0xfffb\\n    then\\n        c7, c6, c5, c4, c3, c2, c1, c0 = 0, 0, 0, 0, 0, 0, 0, c0 - 0xfffb\\n    end\\n\\n    -- Decode s.\\n    local s0, s1, s2, s3 = u4x4(fmt4x4, key, 17)\\n\\n    -- Add.\\n    local t0 =           s0          + c0 + c1  local u0 = t0 % 2 ^ 32\\n    local t1 = t0 - u0 + s1 * 2 ^ 32 + c2 + c3  local u1 = t1 % 2 ^ 64\\n    local t2 = t1 - u1 + s2 * 2 ^ 64 + c4 + c5  local u2 = t2 % 2 ^ 96\\n    local t3 = t2 - u2 + s3 * 2 ^ 96 + c6 + c7  local u3 = t3 % 2 ^ 128\\n\\n    -- Encode.\\n    return p4x4(fmt4x4, u0, u1 / 2 ^ 32, u2 / 2 ^ 64, u3 / 2 ^ 96)\\nend\\n\\nreturn {\\n    mac = mac,\\n}\\n\",\"size\":4574,\"sha256\":\"7184ab3a15c855034d1472e57b6432561e86b33b6c7eaba14863a870219c8a69\"},\"ccryptolib/random.lua\":{\"content\":\"local expect = require \\\"cc.expect\\\".expect\\nlocal hw = require \\\"ccryptolib.internal.hw\\\"\\nlocal blake3 = require \\\"ccryptolib.blake3\\\"\\nlocal chacha20 = require \\\"ccryptolib.chacha20\\\"\\nlocal util = require \\\"ccryptolib.internal.util\\\"\\n\\nlocal lassert = util.lassert\\n\\n-- Extract local context.\\nlocal ctx = {\\n    \\\"ccryptolib 2023-04-11T19:43Z random.lua initialization context\\\",\\n    os.epoch(\\\"utc\\\"),\\n    os.day(),\\n    os.time(),\\n    math.random(0, 2 ^ 24 - 1),\\n    math.random(0, 2 ^ 24 - 1),\\n    tostring({}),\\n    tostring({}),\\n}\\n\\nlocal state = blake3.digest(table.concat(ctx, \\\"|\\\"))\\nlocal initialized = false\\nlocal hwMixed = false\\n\\n--- Mixes random bytes taken by the last compatible peripheral we interacted\\n--- with, if any.\\n---\\n--- takeSeed resets to nil once called, so this function only does work when a\\n--- peripheral is (re-)attached.\\nlocal function mixHwSeed()\\n    local seed = hw.takeSeed()\\n    if not seed then return end\\n    state = blake3.digestKeyed(state, seed)\\n    hwMixed = true\\nend\\n\\n--- Mixes entropy into the generator, and marks it as initialized.\\n--- @param seed string The seed data.\\nlocal function init(seed)\\n    expect(1, seed, \\\"string\\\")\\n    mixHwSeed()\\n    state = blake3.digestKeyed(state, seed)\\n    initialized = true\\nend\\n\\n--- Returns whether the generator has been initialized or not.\\n--- @return boolean\\nlocal function isInit()\\n    return initialized\\nend\\n\\n--- Initializes the generator using VM instruction timing noise.\\n---\\n--- This function counts how many instructions the VM can execute within a single\\n--- millisecond, and mixes the lower bits of these values into the generator state.\\n--- The current implementation collects data for 512 ms and takes the lower 8 bits from\\n--- each count.\\n---\\n--- Compared to fetching entropy from a trusted web source, this approach is riskier but\\n--- more convenient. The factors that influence instruction timing suggest that this\\n--- seed is unpredictable for other players, but this assumption might turn out to be\\n--- untrue.\\nlocal function initWithTiming()\\n    mixHwSeed()\\n    assert(os.epoch(\\\"utc\\\") ~= 0)\\n\\n    local f = assert(load(\\\"local e=os.epoch return{\\\" .. (\\\"e'utc',\\\"):rep(256) .. \\\"}\\\"))\\n\\n    do -- Warmup.\\n        local t = f()\\n        while t[256] - t[1] > 1 do t = f() end\\n    end\\n\\n    -- Fill up the buffer.\\n    local buf = {}\\n    for i = 1, 512 do\\n        local t = f()\\n        while t[256] == t[1] do t = f() end\\n        for j = 1, 256 do\\n            if t[j] ~= t[1] then\\n                buf[i] = j - 1\\n                break\\n            end\\n        end\\n    end\\n\\n    -- Perform a histogram check to catch faulty os.epoch implementations.\\n    local hist = {}\\n    for i = 0, 255 do hist[i] = 0 end\\n    for i = 1, #buf do hist[buf[i]] = hist[buf[i]] + 1 end\\n    for i = 0, 255 do assert(hist[i] < 20) end\\n\\n    init(string.char(table.unpack(buf)))\\nend\\n\\n--- Initializes the generator with the best available source.\\n---\\n--- If no other sources are found, this function will resort to `initWithTiming`\\n--- as entropy source. See its documentation for security caveats.\\nlocal function initAuto()\\n    mixHwSeed()\\n    if not hwMixed then initWithTiming() end\\n    initialized = true\\nend\\n\\n--- Mixes extra entropy into the generator state.\\n--- @param data string The additional entropy to mix.\\nlocal function mix(data)\\n    expect(1, data, \\\"string\\\")\\n    mixHwSeed()\\n    state = blake3.digestKeyed(state, data)\\nend\\n\\n--- Generates random bytes.\\n--- @param len number The desired output length.\\n--- @return string bytes\\nlocal function random(len)\\n    len = math.max(0, expect(1, len, \\\"number\\\"))\\n    lassert(initialized, \\\"attempt to use an uninitialized random generator\\\", 2)\\n    local ok, out = hw.random(len)\\n    if ok then return out end\\n\\n    mixHwSeed()\\n    local msg = (\\\"\\\\0\\\"):rep(len + 32)\\n    local nonce = (\\\"\\\\0\\\"):rep(12)\\n    local out = chacha20.crypt(state, nonce, msg, 8, 0)\\n    state = out:sub(1, 32)\\n    return out:sub(33)\\nend\\n\\nreturn {\\n    init = init,\\n    isInit = isInit,\\n    initWithTiming = initWithTiming,\\n    initAuto = initAuto,\\n    mix = mix,\\n    random = random,\\n}\\n\",\"size\":4041,\"sha256\":\"bd55dc49f579bf808ea3cca624218e8651fa1da658ba8242a2bd948212f59f48\"},\"ccryptolib/sha256.lua\":{\"content\":\"--- The SHA256 cryptographic hash function.\\n\\nlocal expect = require \\\"cc.expect\\\".expect\\nlocal lassert = require \\\"ccryptolib.internal.util\\\".lassert\\nlocal packing = require \\\"ccryptolib.internal.packing\\\"\\nlocal hw = require \\\"ccryptolib.internal.hw\\\"\\n\\nlocal rol = bit32.lrotate\\nlocal shr = bit32.rshift\\nlocal bxor = bit32.bxor\\nlocal bnot = bit32.bnot\\nlocal band = bit32.band\\nlocal unpack = unpack or table.unpack\\nlocal p1x8, fmt1x8 = packing.compilePack(\\\">I8\\\")\\nlocal p16x4, fmt16x4 = packing.compilePack(\\\">I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4I4\\\")\\nlocal u16x4 = packing.compileUnpack(fmt16x4)\\nlocal p8x4, fmt8x4 = packing.compilePack(\\\">I4I4I4I4I4I4I4I4\\\")\\nlocal u8x4 = packing.compileUnpack(fmt8x4)\\n\\nlocal function primes(n, exp)\\n    local out = {}\\n    local p = 2\\n    for i = 1, n do\\n        out[i] = bxor(p ^ exp % 1 * 2 ^ 32)\\n        repeat p = p + 1 until 2 ^ p % p == 2\\n    end\\n    return out\\nend\\n\\nlocal K = primes(64, 1 / 3)\\n\\nlocal h0 = primes(8, 1 / 2)\\n\\nlocal function compress(h, w)\\n    local h0, h1, h2, h3, h4, h5, h6, h7 = unpack(h)\\n    local K = K\\n\\n    -- Message schedule.\\n    for j = 17, 64 do\\n        local wf = w[j - 15]\\n        local w2 = w[j - 2]\\n        local s0 = bxor(rol(wf, 25), rol(wf, 14), shr(wf, 3))\\n        local s1 = bxor(rol(w2, 15), rol(w2, 13), shr(w2, 10))\\n        w[j] = w[j - 16] + s0 + w[j - 7] + s1\\n    end\\n\\n    -- Block.\\n    local a, b, c, d, e, f, g, h = h0, h1, h2, h3, h4, h5, h6, h7\\n    for j = 1, 64 do\\n        local s1 = bxor(rol(e, 26), rol(e, 21), rol(e, 7))\\n        local ch = bxor(band(e, f), band(bnot(e), g))\\n        local temp1 = h + s1 + ch + K[j] + w[j]\\n        local s0 = bxor(rol(a, 30), rol(a, 19), rol(a, 10))\\n        local maj = bxor(band(a, b), band(a, c), band(b, c))\\n        local temp2 = s0 + maj\\n\\n        h = g\\n        g = f\\n        f = e\\n        e = d + temp1\\n        d = c\\n        c = b\\n        b = a\\n        a = temp1 + temp2\\n    end\\n\\n    return {\\n        (h0 + a) % 2 ^ 32,\\n        (h1 + b) % 2 ^ 32,\\n        (h2 + c) % 2 ^ 32,\\n        (h3 + d) % 2 ^ 32,\\n        (h4 + e) % 2 ^ 32,\\n        (h5 + f) % 2 ^ 32,\\n        (h6 + g) % 2 ^ 32,\\n        (h7 + h) % 2 ^ 32,\\n    }\\nend\\n\\n--- Hashes data using SHA256.\\n--- @param data string Input bytes.\\n--- @return string hash The 32-byte hash value.\\nlocal function digest(data)\\n    expect(1, data, \\\"string\\\")\\n    local ok, out = hw.sha256(data)\\n    if ok then return out end\\n\\n    -- Pad input.\\n    local bitlen = #data * 8\\n    local padlen = -(#data + 9) % 64\\n    data = data .. \\\"\\\\x80\\\" .. (\\\"\\\\0\\\"):rep(padlen) .. p1x8(fmt1x8, bitlen)\\n\\n    -- Digest.\\n    local h = h0\\n    for i = 1, #data, 64 do\\n        h = compress(h, {u16x4(fmt16x4, data, i)})\\n    end\\n\\n    return p8x4(fmt8x4, unpack(h))\\nend\\n\\n-- Reports once every ~10ms on a standard CCEmuX emulator.\\nlocal PBKDF2_CB_ITERATIONS = 50\\n\\n--- Hashes a password using PBKDF2-HMAC-SHA256.\\n--- @param password string The password to hash.\\n--- @param salt string The password's salt.\\n--- @param iter number The number of iterations to perform.\\n--- @param progress fun(iter: number)? An optional function to periodically call with the current iteration number as argument.\\n--- @return string dk The 32-byte derived key.\\nlocal function pbkdf2(password, salt, iter, progress)\\n    expect(1, password, \\\"string\\\")\\n    expect(2, salt, \\\"string\\\")\\n    expect(3, iter, \\\"number\\\")\\n    lassert(iter % 1 == 0, \\\"iteration number must be an integer\\\", 2)\\n    lassert(iter > 0, \\\"iteration number must be positive\\\", 2)\\n    expect(4, progress, \\\"function\\\", \\\"nil\\\")\\n\\n    -- Pad password.\\n    if #password > 64 then password = digest(password) end\\n    password = {u16x4(fmt16x4, password .. (\\\"\\\\0\\\"):rep(64), 1)}\\n\\n    -- Compute password blocks.\\n    local ikp = {}\\n    local okp = {}\\n    for i = 1, 16 do\\n        ikp[i] = bxor(password[i], 0x36363636)\\n        okp[i] = bxor(password[i], 0x5c5c5c5c)\\n    end\\n\\n    local hikp = compress(h0, ikp)\\n    local hokp = compress(h0, okp)\\n\\n    -- 96-byte padding.\\n    local pad96 = {2 ^ 31, 0, 0, 0, 0, 0, 0, 0x300}\\n\\n    -- First iteration.\\n    local pre = p16x4(fmt16x4, unpack(ikp))\\n    local hs = {u8x4(fmt8x4, digest(pre .. salt .. \\\"\\\\0\\\\0\\\\0\\\\1\\\"), 1)}\\n    for i = 1, 8 do hs[i + 8] = pad96[i] end\\n    hs = compress(hokp, hs)\\n\\n    -- Second iteration onwards.\\n    local out = {unpack(hs)}\\n    for i = 2, iter do\\n        for j = 1, 8 do hs[j + 8] = pad96[j] end\\n        hs = compress(hikp, hs)\\n        for j = 1, 8 do hs[j + 8] = pad96[j] end\\n        hs = compress(hokp, hs)\\n        for j = 1, 8 do out[j] = bxor(out[j], hs[j]) end\\n        if progress and i % PBKDF2_CB_ITERATIONS == 0 then progress(i) end\\n    end\\n\\n    return p8x4(fmt8x4, unpack(out))\\nend\\n\\nreturn {\\n    digest = digest,\\n    pbkdf2 = pbkdf2,\\n}\\n\",\"size\":4652,\"sha256\":\"956557af41b419e18c5214d9ed337fb905531e23de4b7f53bfa6a8699f52c64e\"},\"ccryptolib/util.lua\":{\"content\":\"--- General utilities for handling byte strings.\\n\\nlocal expect = require \\\"cc.expect\\\".expect\\nlocal random = require \\\"ccryptolib.random\\\"\\nlocal poly1305 = require \\\"ccryptolib.poly1305\\\"\\n\\n--- Returns the hexadecimal version of a string.\\n--- @param str string A string.\\n--- @return string hex The hexadecimal version of the string.\\nlocal function toHex(str)\\n    expect(1, str, \\\"string\\\")\\n    return (\\\"%02x\\\"):rep(#str):format(str:byte(1, -1))\\nend\\n\\n--- Converts back a string from hexadecimal.\\n--- @param hex string A hexadecimal string.\\n--- @return string? str The original string, or nil if the input is invalid.\\nlocal function fromHex(hex)\\n    expect(1, hex, \\\"string\\\")\\n    local out = {}\\n    local n = 0\\n    for c in hex:gmatch(\\\"%x%x\\\") do\\n        n = n + 1\\n        out[n] = tonumber(c, 16)\\n    end\\n    if 2 * n == #hex then return string.char(table.unpack(out)) end\\nend\\n\\n--- Compares two strings while mitigating secret leakage through timing.\\n--- @param a string\\n--- @param b string\\n--- @return boolean eq Whether a == b.\\nlocal function compare(a, b)\\n    expect(1, a, \\\"string\\\")\\n    expect(2, b, \\\"string\\\")\\n    if #a ~= #b then return false end\\n    local kaux = random.random(32)\\n    return poly1305.mac(kaux, a) == poly1305.mac(kaux, b)\\nend\\n\\nreturn {\\n    toHex = toHex,\\n    fromHex = fromHex,\\n    compare = compare,\\n}\\n\",\"size\":1312,\"sha256\":\"cdce1c0e75a4e56097853e4aa85fb76366866078e4694db00ebc958785c96caf\"},\"ccryptolib/x25519.lua\":{\"content\":\"--- The X25519 key exchange scheme.\\n\\nlocal expect = require \\\"cc.expect\\\".expect\\nlocal lassert = require \\\"ccryptolib.internal.util\\\".lassert\\nlocal hw = require \\\"ccryptolib.internal.hw\\\"\\nlocal util = require \\\"ccryptolib.internal.util\\\"\\nlocal c25 = require \\\"ccryptolib.internal.curve25519\\\"\\n\\n--- Computes the public key from a secret key.\\n--- @param sk string A random 32-byte secret key.\\n--- @return string pk The matching public key.\\nlocal function publicKey(sk)\\n    expect(1, sk, \\\"string\\\")\\n    assert(#sk == 32, \\\"secret key length must be 32\\\")\\n    local ok, out = hw.x25519PublicKey(sk)\\n    if ok then return out end\\n    return c25.encode(c25.scale(c25.mulG(util.bits(sk))))\\nend\\n\\n--- Performs the key exchange.\\n--- @param sk string A Curve25519 secret key.\\n--- @param pk string A public key, usually derived from someone else's secret key.\\n--- @return string ss The 32-byte shared secret between both keys.\\nlocal function exchange(sk, pk)\\n    expect(1, sk, \\\"string\\\")\\n    lassert(#sk == 32, \\\"secret key length must be 32\\\", 2)\\n    expect(2, pk, \\\"string\\\")\\n    lassert(#pk == 32, \\\"public key length must be 32\\\", 2) --- @cast pk String32\\n    local ok, out = hw.x25519Exchange(sk, pk)\\n    if ok then return out end\\n    return c25.encode(c25.scale(c25.ladder8(c25.decode(pk), util.bits8(sk))))\\nend\\n\\nreturn {\\n    publicKey = publicKey,\\n    exchange = exchange,\\n}\\n\",\"size\":1350,\"sha256\":\"f8618a0641c48721f21ea1dfc63a06eafb75efc4be6fbcc6a2939dc80a55c72b\"},\"ccryptolib/x25519c.lua\":{\"content\":\"local expect = require \\\"cc.expect\\\".expect\\nlocal lassert = require \\\"ccryptolib.internal.util\\\".lassert\\nlocal hw = require \\\"ccryptolib.internal.hw\\\"\\nlocal fq = require \\\"ccryptolib.internal.fq\\\"\\nlocal fp = require \\\"ccryptolib.internal.fp\\\"\\nlocal c25 = require \\\"ccryptolib.internal.curve25519\\\"\\nlocal sha512 = require \\\"ccryptolib.internal.sha512\\\"\\nlocal random = require \\\"ccryptolib.random\\\"\\n\\n--- Masks an exchange secret key.\\n--- @param sk string A random 32-byte Curve25519 secret key.\\n--- @return string msk A masked secret key.\\nlocal function mask(sk)\\n    expect(1, sk, \\\"string\\\")\\n    lassert(#sk == 32, \\\"secret key length must be 32\\\", 2)\\n    local mask = random.random(32)\\n    local x = fq.decodeClamped(sk)\\n    local r = fq.decodeClamped(mask)\\n    local xr = fq.sub(x, r)\\n    return fq.encode(xr) .. mask\\nend\\n\\n--- Masks a signature secret key.\\n--- @param sk string A random 32-byte Edwards25519 secret key.\\n--- @return string msk A masked secret key.\\nlocal function maskS(sk)\\n    expect(1, sk, \\\"string\\\")\\n    lassert(#sk == 32, \\\"secret key length must be 32\\\", 2)\\n    return mask(sha512.digest(sk):sub(1, 32))\\nend\\n\\n--- Rerandomizes the masking on a masked key.\\n--- @param msk string A masked secret key.\\n--- @return string msk The same secret key, but with another mask.\\nlocal function remask(msk)\\n    expect(1, msk, \\\"string\\\")\\n    lassert(#msk == 64, \\\"masked secret key length must be 64\\\", 2)\\n    local newMask = random.random(32)\\n    local xr = fq.decode(msk:sub(1, 32))\\n    local r = fq.decodeClamped(msk:sub(33))\\n    local s = fq.decodeClamped(newMask)\\n    local xs = fq.add(xr, fq.sub(r, s))\\n    return fq.encode(xs) .. newMask\\nend\\n\\n--- Returns the ephemeral exchange secret key of this masked key.\\n--- This is the second secret key in the \\\"double key exchange\\\" in @{exchange},\\n--- the first being the key that has been masked. The ephemeral key changes\\n--- every time @{remask} is called.\\n--- @param msk string A masked secret key.\\n--- @return string esk The ephemeral half of the masked secret key.\\nlocal function ephemeralSk(msk)\\n    expect(1, msk, \\\"string\\\")\\n    lassert(#msk == 64, \\\"masked secret key length must be 64\\\", 2)\\n    return msk:sub(33)\\nend\\n\\n-- This does not have the same behavior on twist points. It is technically\\n-- inconsistent, but those points are all adversarial inputs, so we may as well\\n-- not care.\\nlocal function hwExchangeOnPoint(sk, P)\\n    local ok1, rP = hw.x25519Exchange(sk:sub(33), P)\\n    if not ok1 then return end\\n\\n    local xr = fq.decode(sk:sub(1, 32))\\n    local r = fq.decodeClamped(sk:sub(33))\\n    local x = fq.add(xr, r)\\n\\n    local ok2, xP = hw.x25519Exchange(fq.encodeClamped(x), P)\\n    if not ok2 then return end\\n\\n    return true, xP, rP\\nend\\n\\nlocal function exchangeOnPoint(sk, P)\\n    local xr = fq.decode(sk:sub(1, 32))\\n    local r = fq.decodeClamped(sk:sub(33))\\n    local rP, xrP, dP = c25.prac(P, fq.makeRuleset(fq.eighth(r), fq.eighth(xr)))\\n\\n    -- Return early if P has small order or if r = xr. (1)\\n    if not rP then\\n        local out = fp.encode(fp.num(0))\\n        return out, out\\n    end\\n\\n    local xP = c25.dadd(dP, rP, xrP)\\n\\n    -- Extract coordinates for scaling.\\n    local Px, Pz = P[1], P[2]\\n    local xPx, xPz = xP[1], xP[2]\\n    local rPx, rPz = rP[1], rP[2]\\n\\n    -- Ensure all Z coordinates are squares.\\n    Px, Pz = fp.mul(Px, Pz), fp.square(Pz)\\n    xPx, xPz = fp.mul(xPx, xPz), fp.square(xPz)\\n    rPx, rPz = fp.mul(rPx, rPz), fp.square(rPz)\\n\\n    -- We're splitting the secret x into (x - r (mod q), r). The multiplication\\n    -- adds them back together, but this only works if P's order is q, which is\\n    -- not the case on the twist.\\n    -- As a result, we need to check if P is on the twist and return 0 so as to\\n    -- not leak part of x. We do this by checking the curve equation against P.\\n    -- The projective equation for curve25519 is Y\\u00b2Z = X(X\\u00b2 + AXZ + Z\\u00b2). Since Z\\n    -- is a square, checking validity means checking the right-hand side to be a\\n    -- square.\\n    local Px2 = fp.square(Px)\\n    local Pz2 = fp.square(Pz)\\n    local Pxz = fp.mul(Px, Pz)\\n    local APxz = fp.kmul(Pxz, 486662)\\n    local rhs = fp.mul(Px, fp.add(Px2, fp.carry(fp.add(APxz, Pz2))))\\n\\n    -- Find the square root of 1 / (rhs * xPz * rPz).\\n    -- Neither rPz, xPz, nor rhs are 0:\\n    -- - If rhs was 0, then P would be low order, which would return at (1).\\n    -- - Since P isn't low order, clamping prevents the ladder from returning O.\\n    -- Since we've just squared both xPz and rPz, the root will exist iff rhs is\\n    -- a square. This checks the curve equation, so we're done.\\n    local root = fp.sqrtDiv(fp.num(1), fp.mul(fp.mul(xPz, rPz), rhs))\\n    if not root then\\n        local out = fp.encode(fp.num(0))\\n        return out, out\\n    end\\n\\n    -- Get the inverses of both Z values.\\n    local xPzrPzInv = fp.mul(fp.square(root), rhs)\\n    local xPzInv = fp.mul(xPzrPzInv, rPz)\\n    local rPzInv = fp.mul(xPzrPzInv, xPz)\\n\\n    -- Finish scaling and encode the output.\\n    return fp.encode(fp.mul(xPx, xPzInv)), fp.encode(fp.mul(rPx, rPzInv))\\nend\\n\\nlocal G_ENC = fp.encode(fp.num(9))\\n\\n--- Returns the X25519 public key of this masked key.\\n--- @param msk string A masked secret key.\\nlocal function publicKey(msk)\\n    expect(1, msk, \\\"string\\\")\\n    lassert(#msk == 64, \\\"masked secret key length must be 64\\\", 2)\\n    local ok, xP, rP = hwExchangeOnPoint(msk, G_ENC)\\n    if ok then return xP, rP end\\n    return (exchangeOnPoint(msk, c25.G))\\nend\\n\\n--- Performs a double key exchange.\\n---\\n--- Returns 0 if the input public key has small order or if it isn't in the base\\n--- curve. This is different from standard X25519, which performs the exchange\\n--- even on the twist.\\n---\\n--- May incorrectly return 0 with negligible chance if the mask happens to match\\n--- the masked key. I haven't checked if clamping prevents that from happening.\\n---\\n--- @param sk string A masked secret key.\\n--- @param pk string An X25519 public key.\\n--- @return string sss The shared secret between the public key and the static half of the masked key.\\n--- @return string sse The shared secret betwen the public key and the ephemeral half of the masked key.\\nlocal function exchange(sk, pk)\\n    expect(1, sk, \\\"string\\\")\\n    lassert(#sk == 64, \\\"masked secret key length must be 64\\\", 2)\\n    expect(2, pk, \\\"string\\\")\\n    lassert(#pk == 32, \\\"public key length must be 32\\\", 2) --- @cast pk String32\\n    local ok, xP, rP = hwExchangeOnPoint(sk, pk)\\n    if ok then return xP, rP end\\n    return exchangeOnPoint(sk, c25.decode(pk))\\nend\\n\\nreturn {\\n    mask = mask,\\n    remask = remask,\\n    publicKey = publicKey,\\n    ephemeralSk = ephemeralSk,\\n    exchange = exchange,\\n}\\n\",\"size\":6568,\"sha256\":\"1dcf554322d946a426bc04cfb4936ab740aa75078fa7209b6ac1b032d4fbe222\"},\"ccryptolib-LICENSE\":{\"content\":\"MIT License\\n\\nCopyright (c) 2023 Miguel Oliveira\\n\\nPermission is hereby granted, free of charge, to any person obtaining a copy\\nof this software and associated documentation files (the \\\"Software\\\"), to deal\\nin the Software without restriction, including without limitation the rights\\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\\ncopies of the Software, and to permit persons to whom the Software is\\nfurnished to do so, subject to the following conditions:\\n\\nThe above copyright notice and this permission notice shall be included in all\\ncopies or substantial portions of the Software.\\n\\nTHE SOFTWARE IS PROVIDED \\\"AS IS\\\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\\nSOFTWARE.\\n\",\"size\":1072,\"sha256\":\"623ba31784b519f69723a296836782e8aedfaec3e9402dfc045bb735ed502824\"},\"ecnet-LICENSE\":{\"content\":\"MIT License\\n\\nCopyright (c) 2020 Miguel Oliveira\\n\\nPermission is hereby granted, free of charge, to any person obtaining a copy\\nof this software and associated documentation files (the \\\"Software\\\"), to deal\\nin the Software without restriction, including without limitation the rights\\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\\ncopies of the Software, and to permit persons to whom the Software is\\nfurnished to do so, subject to the following conditions:\\n\\nThe above copyright notice and this permission notice shall be included in all\\ncopies or substantial portions of the Software.\\n\\nTHE SOFTWARE IS PROVIDED \\\"AS IS\\\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\\nSOFTWARE.\\n\",\"size\":1072,\"sha256\":\"18f00f2ca1ef9d09369a0c2809f0d6737b09011bde3e72c7769d21fda761dd3e\"},\"ecnet2/address_encoder.lua\":{\"content\":\"local expect = require \\\"cc.expect\\\"\\n\\nlocal band = bit32.band\\n\\nlocal alphabet = {}\\nlocal ralphabet = {}\\ndo\\n    local s = \\\"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_\\\"\\n    for i, ch in (s):gmatch(\\\"()(.)\\\") do\\n        alphabet[i - 1] = ch\\n        ralphabet[ch] = i - 1\\n    end\\nend\\n\\n--- Encodes a public key to an address.\\n--- @param publicKey string The 32-byte public key.\\n--- @return string address The encoded address.\\nlocal function encode(publicKey)\\n    expect(1, publicKey, \\\"string\\\")\\n    assert(#publicKey == 32, \\\"invalid public key\\\")\\n    publicKey = publicKey .. \\\"\\\\0\\\"\\n    local out = \\\"\\\"\\n    for block in (publicKey):gmatch(\\\"...\\\") do\\n        local val = (\\\">I3\\\"):unpack(block)\\n        local mul = 2 ^ -18\\n        for _ = 0, 18, 6 do\\n            out = out .. alphabet[band(val * mul, 63)]\\n            mul = mul * 64\\n        end\\n    end\\n    return out:sub(1, 43) .. \\\"=\\\"\\nend\\n\\n--- Decodes an address to a public key.\\n--- @param address string The address to decode.\\n--- @return string? publicKey The decoded public key, or nil on failure.\\nlocal function parse(address)\\n    if type(address) ~= \\\"string\\\" then return end\\n    if #address ~= 44 then return end\\n    if not address:match(\\\"^[A-Za-z0-9%-_]*=$\\\") then return end\\n    address = address:sub(1, 43) .. \\\"A\\\"\\n    local bytes = \\\"\\\"\\n    for block in address:gmatch(\\\"....\\\") do\\n        local val = 0\\n        for i = 1, 4 do val = val * 64 + ralphabet[block:sub(i, i)] end\\n        bytes = bytes .. (\\\">I3\\\"):pack(val)\\n    end\\n    return bytes:sub(1, 32)\\nend\\n\\nreturn {\\n    encode = encode,\\n    parse = parse,\\n}\\n\",\"size\":1569,\"sha256\":\"c0f9ec8face403f024870ccfd8093aa0e1170ce720a93ce2435e911a05832e1d\"},\"ecnet2/cipher_state.lua\":{\"content\":\"local class = require \\\"ecnet2.class\\\"\\nlocal aead = require \\\"ccryptolib.aead\\\"\\nlocal chacha = require \\\"ccryptolib.chacha20\\\"\\n\\n--- A symmetric encryption cipher state, containing a key and a numeric nonce.\\n--- @class ecnet2.CipherState\\n--- @field private k string? The current key.\\n--- @field private n number The current nonce.\\nlocal CipherState = class \\\"ecnet2.CipherState\\\"\\n\\n--- @param key string? A 32-byte key to initialize the state with.\\nfunction CipherState:initialise(key)\\n    self.k = key\\n    self.n = 0\\nend\\n\\n--- Whether the state has a key or not.\\n--- @return boolean\\nfunction CipherState:hasKey()\\n    return self.k ~= nil\\nend\\n\\n--- Sets the nonce to the given value.\\n--- @param nonce number\\nfunction CipherState:setNonce(nonce)\\n    self.n = nonce\\nend\\n\\n--- Rekeys the cipher.\\nfunction CipherState:rekey()\\n    self.k = chacha.crypt(self.k, (\\\"<I12\\\"):pack(2 ^ 64 - 1), (\\\"\\\\0\\\"):rep(32), 8)\\nend\\n\\n--- Computes the cipher descriptor.\\n--- @return string\\nfunction CipherState:descriptor()\\n    local c = chacha.crypt(self.k, (\\\"<I12\\\"):pack(2 ^ 64 - 1), (\\\"\\\\0\\\"):rep(64), 8)\\n    return c:sub(33)\\nend\\n\\n--- Encrypts a message. Returns the plaintext itself when no key is set.\\n--- @param ad string Associated data to authenticate.\\n--- @param plaintext string The plaintext to encrypt\\n--- @return string ciphertext The encrypted text.\\nfunction CipherState:encryptWithAd(ad, plaintext)\\n    if self:hasKey() then\\n        local nonce = (\\\"<I12\\\"):pack(self.n)\\n        local ctx, tag = aead.encrypt(self.k, nonce, plaintext, ad, 8)\\n        self.n = self.n + 1\\n        return ctx .. tag\\n    else\\n        return plaintext\\n    end\\nend\\n\\n--- Decrypts a message.\\n--- @param ad string Associated data to authenticate.\\n--- @param ciphertext string The ciphertext to decrypt.\\n--- @return string? plaintext The decrypted plaintext, or nil on failure.\\nfunction CipherState:decryptWithAd(ad, ciphertext)\\n    if self:hasKey() then\\n        if #ciphertext < 16 then return end\\n        local ctx, tag = ciphertext:sub(1, -17), ciphertext:sub(-16)\\n        local nonce = (\\\"<I12\\\"):pack(self.n)\\n        -- On decryption failure, increment nonce and return nil.\\n        local plaintext = aead.decrypt(self.k, nonce, tag, ctx, ad, 8)\\n        self.n = self.n + 1\\n        return plaintext\\n    else\\n        return ciphertext\\n    end\\nend\\n\\nreturn CipherState\\n\",\"size\":2311,\"sha256\":\"a9637d2ee84976c3e312583d7733e36f8f1d1e9c82321f01413478ac21662f7f\"},\"ecnet2/class.lua\":{\"content\":\"--[[\\nCopyright 2018-2023 SquidDev\\n\\nRedistribution and use in source and binary forms, with or without modification,\\nare permitted provided that the following conditions are met:\\n\\n1. Redistributions of source code must retain the above copyright notice, this\\n   list of conditions and the following disclaimer.\\n\\n2. Redistributions in binary form must reproduce the above copyright notice,\\n   this list of conditions and the following disclaimer in the documentation\\n   and/or other materials provided with the distribution.\\n\\n3. Neither the name of the copyright holder nor the names of its contributors\\n   may be used to endorse or promote products derived from this software without\\n   specific prior written permission.\\n\\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \\\"AS IS\\\" AND\\nANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED\\nWARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\\nDISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE\\nFOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\\nDAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR\\nSERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER\\nCAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,\\nOR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF\\nTHIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\\n]]\\n\\n--- A tiny \\\"class\\\" implementation.\\n--\\n-- This does not support inheritance, operators, or anything complex - it's\\n-- just a way of enabling method calls.\\n\\nlocal expect = require \\\"cc.expect\\\".expect\\n\\nlocal function tostring_instance(self) return self.__name .. \\\"<>\\\" end\\nlocal function tostring_class(self) return \\\"Class<\\\" .. self.__name .. \\\">\\\" end\\n\\nlocal class_mt = {\\n  __tostring = tostring_class,\\n  __name = \\\"class\\\",\\n  __call = function(self, ...)\\n    local tbl = setmetatable({}, self.__index)\\n    tbl:initialise(...)\\n    return tbl\\n  end,\\n}\\n\\nreturn function(name)\\n  expect(1, name, \\\"string\\\")\\n\\n  local class = setmetatable({\\n    __name = name,\\n    __tostring = tostring_instance,\\n  }, class_mt)\\n\\n  class.__index = class\\n  return class\\nend\\n\",\"size\":2222,\"sha256\":\"7223269921c5aba6b7d3f2ffdcbdb6bf0d2355a71dc2e5136f6afca2a81b584b\"},\"ecnet2/connection.lua\":{\"content\":\"local class = require \\\"ecnet2.class\\\"\\nlocal ecnetd = require \\\"ecnet2.ecnetd\\\"\\nlocal addressEncoder = require \\\"ecnet2.address_encoder\\\"\\nlocal modems = require \\\"ecnet2.modems\\\"\\nlocal uid = require \\\"ecnet2.uid\\\"\\n\\n--- An encrypted tunnel operating over a modem.\\n--- @class ecnet2.Connection\\n--- @field _state ecnet2.HandshakeState The current handshake state.\\n--- @field _protocol ecnet2.Protocol The connection's protocol.\\n--- @field _side string The modem name this connection is routing through.\\n--- @field _handler function The packet handler function.\\n--- @field id string The connection's ID, used in `ecnet2_message` events.\\nlocal Connection = class \\\"ecnet2.Connection\\\"\\n\\n--- @param state ecnet2.HandshakeState\\n--- @param protocol ecnet2.Protocol\\n--- @param side string\\nfunction Connection:initialise(state, protocol, side)\\n    self.id = uid()\\n    self._protocol = protocol\\n    self._side = side\\n    self._handler = function(m, _, c, d) return self:_handle(m, _, c, d) end\\n    self._state = state\\n    if state.d then ecnetd.addHandler(state.d, self._handler) end\\nend\\n\\n--- @param newState ecnet2.HandshakeState\\nfunction Connection:_setState(newState)\\n    if self._state.d then ecnetd.removeHandler(self._state.d) end\\n    if newState.d then ecnetd.addHandler(newState.d, self._handler) end\\n    self._state = newState\\nend\\n\\n--- Handles an incoming packet, modifying the state.\\n--- @param packet string\\n--- @param _ string\\n--- @param ch integer\\n--- @param dist number\\nfunction Connection:_handle(packet, _, ch, dist)\\n    local newState, msg = self._state.resolve(packet)\\n    self:_setState(newState)\\n    if not msg then return end\\n    local deserialize = self._protocol._interface.deserialize\\n    local ok, message = pcall(deserialize, msg)\\n    if ok then\\n        local addr = addressEncoder.encode(self._state.pk)\\n        os.queueEvent(\\\"ecnet2_message\\\", self.id, addr, message, ch, dist)\\n    end\\nend\\n\\n--- Sends a message.\\n---\\n--- Throws `\\\"can't send on an incomplete connection\\\"` until at least one\\n--- message has been received.\\n---\\n--- @param message any The message object.\\nfunction Connection:send(message)\\n    local str = self._protocol._interface.serialize(message)\\n    assert(type(str) == \\\"string\\\", \\\"serializer returned non-string\\\")\\n    assert(self._state.maxlen, \\\"can't send on an incomplete connection\\\")\\n    assert(#str <= self._state.maxlen, \\\"serialized message is too large\\\")\\n    local newState, data = self._state.send(str)\\n    self:_setState(newState)\\n    if data then modems.transmit(self._side, data) end\\nend\\n\\n--- Yields until a message is received. Returns the sender and contents, or nil\\n--- on timeout.\\n--- @param timeout number?\\n--- @return string? sender\\n--- @return any message\\nfunction Connection:receive(timeout)\\n    local timer = -1\\n    if timeout then timer = os.startTimer(timeout) end\\n    while true do\\n        local event, p1, p2, p3 = os.pullEvent()\\n        if event == \\\"timer\\\" and p1 == timer then\\n            return\\n        elseif event == \\\"ecnet2_message\\\" and p1 == self.id then\\n            os.cancelTimer(timer)\\n            return p2, p3\\n        end\\n    end\\nend\\n\\nreturn Connection\\n\",\"size\":3107,\"sha256\":\"44b13336a9790f2ea79cf0c3e8daff8e97300833c2ef5cbfa18d9cc1d89a5a3f\"},\"ecnet2/constants.lua\":{\"content\":\"return {\\n    CHANNEL = 33635,\\n    IDENTITY_PATH = \\\"/.ecnet2\\\",\\n}\\n\",\"size\":64,\"sha256\":\"e90aafe013b70a44f0b64fb4a6a4c693887454ed08f596d10bd55603bace684d\"},\"ecnet2/ecnetd.lua\":{\"content\":\"local constants = require \\\"ecnet2.constants\\\"\\n\\n--- The global daemon state.\\n\\nlocal handlers = setmetatable({}, { __mode = \\\"v\\\" })\\n\\n--- @param message string\\nlocal function enqueue(message, side, ch, dist)\\n    if type(message) ~= \\\"string\\\" then return end\\n    if #message >= 2 ^ 16 then return end\\n    if #message < 32 then return end\\n    local descriptor = message:sub(1, 32)\\n    local etc = message:sub(33)\\n    local handler = handlers[descriptor]\\n    if handler then return handler(etc, side, ch, dist) end\\nend\\n\\nlocal function daemon()\\n    while true do\\n        local _, side, ch, _, msg, dist = coroutine.yield(\\\"modem_message\\\")\\n        if ch == constants.CHANNEL then enqueue(msg, side, ch, dist) end\\n    end\\nend\\n\\nlocal function addHandler(name, fun)\\n    handlers[name] = fun\\nend\\n\\nlocal function removeHandler(name)\\n    handlers[name] = nil\\nend\\n\\n--- @class ecnet2.EcnetdState\\n--- @field daemon fun()\\n--- @field addHandler fun(name: string, fun: function)\\n--- @field removeHandler fun(name: string)\\nreturn {\\n    daemon = daemon,\\n    addHandler = addHandler,\\n    removeHandler = removeHandler,\\n}\\n\",\"size\":1094,\"sha256\":\"70b7ce82acfac46e13e00a0809a5b20f9681561dc77643ee1456b8c0293a0343\"},\"ecnet2/handshake_state.lua\":{\"content\":\"local x25519c = require \\\"ccryptolib.x25519c\\\"\\nlocal x25519 = require \\\"ccryptolib.x25519\\\"\\nlocal SymmetricState = require \\\"ecnet2.symmetric_state\\\"\\n\\n--- 32 null bytes. Used for null public keys and shared secrets.\\nlocal NULL_KEY = (\\\"\\\\0\\\"):rep(32)\\n\\n--- A handshake state.\\n--- @class ecnet2.HandshakeState\\n--- The other party's public key, if known.\\n--- @field pk string?\\n--- A descriptor to filter when receiving.\\n--- @field d string?\\n--- A function to call for resolving incoming messages. The argument shouldn't\\n--- include the descriptor. Returns the next state to use, and the decrypted\\n--- message or nil on failure.\\n--- @field resolve (fun(data: string): ecnet2.HandshakeState, string?)?\\n--- The largest length, in bytes, that send() will accept.\\n--- @field maxlen number\\n--- A function to call for sending messages. Returns the next state to use and\\n--- the raw data to send over the network.\\n--- @field send (fun(msg: string): ecnet2.HandshakeState, string?)?\\n\\n--- Returns a dummy state that does nothing. Used for aborting handshakes.\\n--- @return ecnet2.HandshakeState\\nlocal function close()\\n    return {\\n        maxlen = math.huge,\\n        send = function()\\n            return close(), nil\\n        end,\\n    }\\nend\\n\\n--- Pads a message to partially hide its length.\\n--- @param msg string The input message.\\n--- @param prefixlen number The size of a prefix that will be added afterwards.\\n--- @param minlen number The minimum length to pad the message into.\\nlocal function pad(msg, prefixlen, minlen)\\n    local l = math.max(#msg + prefixlen + 2, minlen)\\n    local e = math.floor(math.log(l, 2))\\n    local s = math.floor(math.log(e, 2)) + 1\\n    local w = l + -l % 2 ^ (e - s)\\n    local p = math.min(w, 2 ^ 16) - #msg - prefixlen - 2\\n    return msg .. \\\"\\\\x80\\\" .. (\\\"\\\\0\\\"):rep(p)\\nend\\n\\n--- Unpads a padded message.\\n--- @param msg string? The padded message, or nil for failure propagation.\\n--- @return string? unpadded The unpadded message, or nil on failure.\\nlocal function unpad(msg)\\n    if not msg then return end\\n    for i = #msg, 1, -1 do\\n        local b = msg:byte(i)\\n        if b == 0 then\\n        elseif b == 0x80 then\\n            return msg:sub(1, i - 1)\\n        else\\n            return\\n        end\\n    end\\nend\\n\\n--- Creates the transport state.\\n--- @param pk string The remote party's public key.\\n--- @param localCs ecnet2.CipherState The local party's cipher state.\\n--- @param remoteCs ecnet2.CipherState The remote party's cipher state.\\n--- @return ecnet2.HandshakeState\\nlocal function Transport(pk, localCs, remoteCs)\\n    local out = {}\\n\\n    out.pk = pk\\n    out.d = localCs:descriptor()\\n    out.maxlen = 2 ^ 16 - 1 - 32 - 1 - 1 - 16\\n\\n    function out.resolve(data)\\n        if #data < 16 then return close() end\\n        local m = unpad(localCs:decryptWithAd(\\\"\\\", data))\\n        localCs:rekey()\\n        if not m then return close() end\\n        return Transport(pk, localCs, remoteCs), m\\n    end\\n\\n    function out.send(msg)\\n        local d = remoteCs:descriptor()\\n        local ctx = remoteCs:encryptWithAd(\\\"\\\", pad(msg, 32 + 16, 192))\\n        remoteCs:rekey()\\n        return Transport(pk, localCs, remoteCs), d .. ctx\\n    end\\n\\n    return out\\nend\\n\\n--- Creates the responder state for message C (-> s, se).\\n--- @param msk string The responder's masked secret key.\\n--- @param symmetricState ecnet2.SymmetricState The handshake's symmetric state.\\n--- @return ecnet2.HandshakeState\\nlocal function rC(msk, symmetricState)\\n    local out = {}\\n\\n    out.d = symmetricState:descriptor()\\n\\n    function out.resolve(data)\\n        if #data < 64 then return close() end\\n\\n        local pk = symmetricState:decryptAndHash(data:sub(1, 48))\\n        if not pk then return close() end\\n\\n        if pk == NULL_KEY then\\n            symmetricState:mixKey(NULL_KEY)\\n        else\\n            symmetricState:mixKey(x25519.exchange(x25519c.ephemeralSk(msk), pk))\\n        end\\n\\n        local xm = unpad(symmetricState:decryptAndHash(data:sub(49)))\\n        if not xm then return close() end\\n        local ok, m = pcall(string.unpack, \\\"<s2\\\", xm)\\n        if not ok then return close() end\\n\\n        local iCs, rCs = symmetricState:split()\\n        return Transport(pk, rCs, iCs), m\\n    end\\n\\n    return out\\nend\\n\\n--- Creates the initiator state for message C (-> s, se).\\n--- @param iPk string The initiator's static public key.\\n--- @param rPk string The responder's static public key.\\n--- @param se string The static-ephemeral shared secret for this handshake.\\n--- @param symmetricState ecnet2.SymmetricState The handshake's symmetric state.\\n--- @return ecnet2.HandshakeState\\nlocal function iC(iPk, rPk, se, symmetricState)\\n    local out = {}\\n\\n    out.maxlen = 2 ^ 15 - 1\\n    out.pk = rPk\\n\\n    function out.send(msg)\\n        local d = symmetricState:descriptor()\\n\\n        local pkCtx = symmetricState:encryptAndHash(iPk)\\n\\n        symmetricState:mixKey(se)\\n        local xmsg = (\\\"<s2\\\"):pack(msg)\\n        local ctx = symmetricState:encryptAndHash(pad(xmsg, 32 + 48 + 16, 192))\\n        local iCs, rCs = symmetricState:split()\\n        return Transport(rPk, iCs, rCs), d .. pkCtx .. ctx\\n    end\\n\\n    return out\\nend\\n\\n--- Creates the initiator state for message B (<- e, ee)\\n--- @param msk string The initiator's masked secret key.\\n--- @param iPk string The initiator's static public key.\\n--- @param rPk string The responder's static public key.\\n--- @param symmetricState ecnet2.SymmetricState The handshake's symmetric state.\\n--- @return ecnet2.HandshakeState\\nlocal function iB(msk, iPk, rPk, symmetricState)\\n    local out = {}\\n\\n    out.d = symmetricState:descriptor()\\n\\n    function out.resolve(data)\\n        if #data < 64 then return close() end\\n\\n        local e = symmetricState:decryptAndHash(data:sub(1, 48))\\n        if not e then return close() end\\n\\n        local se, ee = x25519c.exchange(msk, e)\\n        if ee == NULL_KEY then\\n            ee = x25519.exchange(x25519c.ephemeralSk(msk), e)\\n        end\\n        symmetricState:mixKey(ee)\\n\\n        local xm = unpad(symmetricState:decryptAndHash(data:sub(49)))\\n        if not xm then return close() end\\n        local ok, msg = pcall(string.unpack, \\\"<s2\\\", xm)\\n        if not ok then return close() end\\n\\n        return iC(iPk, rPk, se, symmetricState), msg\\n    end\\n\\n    return out\\nend\\n\\n--- Creates the responder state for message B (<- e, ee).\\n--- @param msk string The responder's masked secret key.\\n--- @param ee string The ephemeral-ephemeral shared secret for this handshake.\\n--- @param symmetricState ecnet2.SymmetricState The handshake's symmetric state.\\n--- @return ecnet2.HandshakeState\\nlocal function rB(msk, ee, symmetricState)\\n    local out = {}\\n\\n    out.maxlen = 2 ^ 15 - 1\\n\\n    function out.send(msg)\\n        local d = symmetricState:descriptor()\\n\\n        local e = x25519.publicKey(x25519c.ephemeralSk(msk))\\n        local eCtx = symmetricState:encryptAndHash(e)\\n\\n        symmetricState:mixKey(ee)\\n        local xmsg = (\\\"<s2\\\"):pack(msg)\\n        local ctx = symmetricState:encryptAndHash(pad(xmsg, 32 + 48 + 16, 192))\\n\\n        return rC(msk, symmetricState), d .. eCtx .. ctx\\n    end\\n\\n    return out\\nend\\n\\n--- Initializes a handshake as the responder, resolving message A (-> e, es).\\n--- @param msk string The responder's masked secret key.\\n--- @param rPk string The responder's static public key.\\n--- @param prologue string The handshake prologue.\\n--- @param introPsk string A pre-shared key resolved in the introduction.\\n--- @param data string The incoming network message, without the intro prefix.\\n--- @return ecnet2.HandshakeState\\nlocal function rA(msk, rPk, prologue, introPsk, data)\\n    if #data < 64 then return close() end\\n\\n    msk = x25519c.remask(msk)\\n\\n    local symmetricState = SymmetricState()\\n    symmetricState:mixHash(prologue)\\n    symmetricState:mixHash(rPk)\\n    symmetricState:mixKeyAndHash(introPsk)\\n\\n    local eCtx = data:sub(1, 48)\\n    local e = symmetricState:decryptAndHash(eCtx)\\n    if not e then return close() end\\n\\n    local es, ee = x25519c.exchange(msk, e)\\n    if ee == NULL_KEY then\\n        ee = x25519.exchange(x25519c.ephemeralSk(msk), e)\\n    end\\n    symmetricState:mixKey(es)\\n\\n    local m = unpad(symmetricState:decryptAndHash(data:sub(49)))\\n    if not m then return close() end\\n\\n    return rB(msk, ee, symmetricState)\\nend\\n\\n--- Returns a unique tag for a given (valid) connection request packet.\\n--- @param data string The incoming network message, without the intro prefix.\\n--- @return string tag A unique tag for this request.\\nlocal function getTag(data)\\n    if #data < 64 then return \\\"\\\" end\\n    return data:sub(33, 48)\\nend\\n\\n--- Initializes a handshake as the initiator, sending message A (-> e, es).\\n--- @param msk string The initiator's masked secret key.\\n--- @param iPk string The initiator's static public key.\\n--- @param rPk string The responder's static public key.\\n--- @param prologue string The handshake prologue.\\n--- @param introPsk string A pre-shared key resolved in the introduction.\\n--- @return ecnet2.HandshakeState\\n--- @return string data The raw data to send over the network.\\nlocal function iA(msk, iPk, rPk, prologue, introPsk)\\n    msk = x25519c.remask(msk)\\n\\n    local symmetricState = SymmetricState()\\n    symmetricState:mixHash(prologue)\\n    symmetricState:mixHash(rPk)\\n    symmetricState:mixKeyAndHash(introPsk)\\n\\n    local esk = x25519c.ephemeralSk(msk)\\n    local eCtx = symmetricState:encryptAndHash(x25519.publicKey(esk))\\n\\n    if rPk == NULL_KEY then\\n        symmetricState:mixKey(NULL_KEY)\\n    else\\n        symmetricState:mixKey(x25519.exchange(esk, rPk))\\n    end\\n    local ctx = symmetricState:encryptAndHash(pad(\\\"\\\", 32 + 48 + 16, 192))\\n\\n    return iB(msk, iPk, rPk, symmetricState), eCtx .. ctx\\nend\\n\\nreturn {\\n    iA = iA,\\n    rA = rA,\\n    getTag = getTag,\\n    close = close,\\n}\\n\",\"size\":9655,\"sha256\":\"e4e43034e217b874cc64febcdad0c3068add6fe1a89c122a9236fc8a523522bb\"},\"ecnet2/identity.lua\":{\"content\":\"local class = require \\\"ecnet2.class\\\"\\nlocal random = require \\\"ccryptolib.random\\\"\\nlocal blake3 = require \\\"ccryptolib.blake3\\\"\\nlocal x25519 = require \\\"ccryptolib.x25519\\\"\\nlocal x25519c = require \\\"ccryptolib.x25519c\\\"\\nlocal addressEncoder = require \\\"ecnet2.address_encoder\\\"\\nlocal Protocol = require \\\"ecnet2.protocol\\\"\\n\\nlocal ID_PATH = \\\"id.bin\\\"\\nlocal ID_DEL_PATH = \\\"id.bin.del\\\"\\nlocal ID_BACKUP_PATH = \\\"id.bin.bak\\\"\\nlocal ADDRESS_PATH = \\\"address.txt\\\"\\n\\nlocal NOISE_SIZE = 512\\n\\n--- @return string\\nlocal function mkNoise()\\n    local body = random.random(NOISE_SIZE - 32)\\n    local checksum = blake3.digest(body)\\n    return checksum .. body\\nend\\n\\n--- @param noise string?\\n--- @return string?\\nlocal function mkKeyFromNoise(noise)\\n    if not noise then return end\\n    local checksum = blake3.digest(noise:sub(33))\\n    if noise:sub(1, 32) ~= checksum then return end\\n    return blake3.digest(noise)\\nend\\n\\n--- @class ecnet2.Identity Identifies a peer to other connected devices.\\n--- @field _msk string The masked secret key for the identity.\\n--- @field _pk string The public key for the identity.\\n--- @field address string The address for connecting to this device\\nlocal Identity = class \\\"ecnet2.Identity\\\"\\n\\n--- @param path string?\\nfunction Identity:initialise(path)\\n    local sk = nil\\n    if not path then\\n        sk = random.random(32)\\n    else\\n        local idPath = fs.combine(path, ID_PATH)\\n        local idDelPath = fs.combine(path, ID_DEL_PATH)\\n        local idBackupPath = fs.combine(path, ID_BACKUP_PATH)\\n\\n        --#region critical section on the directory\\n        fs.makeDir(path)\\n        if fs.exists(idDelPath) then\\n            fs.delete(path)\\n            fs.makeDir(path)\\n        end\\n\\n        local noise\\n        if fs.exists(idPath) then\\n            local f = assert(fs.open(idPath, \\\"rb\\\"))\\n            noise = f.readAll()\\n            f.close()\\n        else\\n            noise = mkNoise()\\n            local f = assert(fs.open(idDelPath, \\\"wb\\\"))\\n            f.write(noise)\\n            f.close()\\n            fs.copy(idDelPath, idBackupPath)\\n            fs.move(idDelPath, idPath)\\n        end\\n\\n        sk = assert(mkKeyFromNoise(noise), \\\"identity file is corrupted\\\")\\n    end\\n\\n    local pk = x25519.publicKey(sk)\\n    local addr = addressEncoder.encode(pk)\\n\\n    if path then\\n        local addressPath = fs.combine(path, ADDRESS_PATH)\\n        local f = assert(fs.open(addressPath, \\\"wb\\\"))\\n        f.write(addr)\\n        f.close()\\n    end\\n    --#endregion\\n\\n    self._msk = x25519c.mask(sk)\\n    self._pk = pk\\n    self.address = addr\\nend\\n\\n--- Creates a protocol from a given interface on this identity.\\n--- @return ecnet2.Protocol\\nfunction Identity:Protocol(interface)\\n    return Protocol(interface, self)\\nend\\n\\nreturn Identity\\n\",\"size\":2706,\"sha256\":\"0d5711b6c5a7e32cb2cd1a4ea1af71c7248b819ac5ba56be039de2a7dad600c1\"},\"ecnet2/init.lua\":{\"content\":\"local constants = require \\\"ecnet2.constants\\\"\\nlocal Identity = require \\\"ecnet2.identity\\\"\\nlocal modems = require \\\"ecnet2.modems\\\"\\nlocal ecnetd = require \\\"ecnet2.ecnetd\\\"\\nlocal expect = require \\\"cc.expect\\\"\\n\\nlocal module = {}\\n\\n--- @type ecnet2.Identity?\\nlocal identity\\n\\nlocal function fetchIdentity()\\n    if not identity then identity = Identity(constants.IDENTITY_PATH) end\\n    return identity\\nend\\n\\n--- Loads or creates an identity file in the given path.\\n--- @param path string? The path to load or create the identity at.\\n--- @return ecnet2.Identity\\nfunction module.Identity(path)\\n    return Identity(expect(1, path, \\\"string\\\", \\\"nil\\\"))\\nend\\n\\n--- The anonymous identity.\\n--- @type ecnet2.Identity\\nmodule.ANONYMOUS = setmetatable({\\n    _msk = \\\"h\\\\x9f\\\\xae\\\\xe7\\\\xd2\\\\x18\\\\x93\\\\xc0\\\\xb2\\\\xe6\\\\xbc\\\\x17\\\\xf5\\\\xce\\\\xf7\\\\xa6\\\\0\\\\0\\\\0\\\\0\\\\0\\\\0\\\\0\\\\0\\\\0\\\\0\\\\0\\\\0\\\\0\\\\0\\\\0@\\\" .. (\\\"\\\\0\\\"):rep(32),\\n    _pk = (\\\"\\\\0\\\"):rep(32),\\n    address = \\\"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\\\",\\n}, Identity)\\n\\n--- Returns the address for this device.\\n--- @deprecated Use `ecnet2.Identity(\\\"/.ecnet2\\\").address` instead.\\n--- @return string address The address.\\nfunction module.address()\\n    return fetchIdentity().address\\nend\\n\\nmodule.open = modems.open\\nmodule.close = modems.close\\nmodule.isOpen = modems.isOpen\\n\\nmodule.daemon = ecnetd.daemon\\n\\n--- Creates a protocol from a given interface.\\n--- @deprecated Use `ecnet2.Identity(\\\"/.ecnet2\\\"):Protocol(...)` instead.\\n--- @param interface ecnet2.IProtocol A table describing the protocol.\\n--- @return ecnet2.Protocol protocol The resulting protocol.\\nfunction module.Protocol(interface)\\n    return fetchIdentity():Protocol(interface)\\nend\\n\\nreturn module\\n\",\"size\":1636,\"sha256\":\"27422d733970ca1ec4c472f5beb732253dc22be0908bcaa3923a2b4356d34541\"},\"ecnet2/listener.lua\":{\"content\":\"local class = require \\\"ecnet2.class\\\"\\nlocal uid = require \\\"ecnet2.uid\\\"\\nlocal blake3 = require \\\"ccryptolib.blake3\\\"\\nlocal ecnetd = require \\\"ecnet2.ecnetd\\\"\\nlocal modems = require \\\"ecnet2.modems\\\"\\nlocal HandshakeState = require \\\"ecnet2.handshake_state\\\"\\nlocal Connection = require \\\"ecnet2.connection\\\"\\n\\n--- A listener for incoming connection requests.\\n--- @class ecnet2.Listener\\n--- @field _protocol ecnet2.Protocol The listener's protocol.\\n--- @field _psk string The PSK for incoming connections in this listener.\\n--- @field _handler function The packet handler function.\\n--- @field _processed table<string, ecnet2.Connection> Processed connections.\\n--- @field id string The connection's ID, used in `ecnet2_message` events.\\nlocal Listener = class \\\"ecnet2.Listener\\\"\\n\\n--- @param protocol ecnet2.Protocol\\nfunction Listener:initialise(protocol)\\n    self.id = uid()\\n    self._psk = blake3.digest(protocol._identity._pk .. protocol._hash)\\n    self._protocol = protocol\\n    self._processed = setmetatable({}, { __mode = \\\"v\\\" })\\n    self._handler = function(m, s, c, d) return self:_handle(m, s, c, d) end\\n    local descriptor = blake3.digest(self._psk)\\n    ecnetd.addHandler(descriptor, self._handler)\\nend\\n\\n--- Handles an incoming packet.\\n--- @param packet string\\n--- @param side string\\n--- @param ch integer\\n--- @param dist number\\nfunction Listener:_handle(packet, side, ch, dist)\\n    local request = { _lid = self.id, _nid = side, _packet = packet }\\n    os.queueEvent(\\\"ecnet2_request\\\", self.id, request, side, ch, dist)\\nend\\n\\n--- Accepts a request and builds a connection. Waits for the next request if\\n--- none are provided.\\n---\\n--- Throws `\\\"invalid listener for this request\\\"` if the supplied request isn't\\n--- meant for this listener.\\n---\\n--- Returns a dummy connection if the request is malformed, or if the request\\n--- has already been accepted.\\n---\\n--- @param reply any\\n--- @param request table?\\n--- @return ecnet2.Connection\\nfunction Listener:accept(reply, request)\\n    -- Wait for the request if not given.\\n    while not request do\\n        local _, id, req = os.pullEvent(\\\"ecnet2_request\\\")\\n        if id == self.id then request = req end\\n    end\\n\\n    -- If the tag has already been processed, return a dummy connection. \\n    local tag = HandshakeState.getTag(request._packet)\\n    if self._processed[tag] then\\n        return Connection(HandshakeState.close(), self._protocol, request._nid)\\n    end\\n\\n    assert(request._lid == self.id, \\\"invalid listener for this request\\\")\\n\\n    local msk = self._protocol._identity._msk\\n    local pk = self._protocol._identity._pk\\n    local state = HandshakeState.rA(msk, pk, \\\"\\\", self._psk, request._packet)\\n\\n    local str = self._protocol._interface.serialize(reply)\\n    assert(type(str) == \\\"string\\\", \\\"serializer returned non-string\\\")\\n    assert(#str <= state.maxlen, \\\"serialized message is too large\\\")\\n    local newState, packet = state.send(str)\\n    if packet then modems.transmit(request._nid, packet) end\\n\\n    local connection = Connection(newState, self._protocol, request._nid)\\n    self._processed[tag] = connection\\n    return connection\\nend\\n\\nreturn Listener\\n\",\"size\":3092,\"sha256\":\"7a40557f09fecc9ef25e55502cc9a44576ad74e7682d5e0528fc2227555245f5\"},\"ecnet2/modems.lua\":{\"content\":\"local expect = require \\\"cc.expect\\\"\\nlocal constants = require \\\"ecnet2.constants\\\"\\n\\n--- Opens a modem with the given peripheral name for exchanging messages.\\n--- @param modem string\\nlocal function open(modem)\\n    expect.expect(1, modem, \\\"string\\\")\\n    assert(peripheral.getType(modem) == \\\"modem\\\", \\\"no such modem: \\\" .. modem)\\n    peripheral.call(modem, \\\"open\\\", constants.CHANNEL)\\nend\\n\\n--- Closes a modem with the given peripheral name, or all modems if not given.\\n--- @param modem string?\\nlocal function close(modem)\\n    expect.expect(1, modem, \\\"string\\\", \\\"nil\\\")\\n    if modem then\\n        assert(peripheral.getType(modem) == \\\"modem\\\", \\\"no such modem: \\\" .. modem)\\n        return peripheral.call(modem, \\\"close\\\", constants.CHANNEL)\\n    else\\n        peripheral.find(\\\"modem\\\", close)\\n    end\\nend\\n\\n--- Returns whether a modem is currently open, or any modem if not given.\\n--- @param modem string?\\n--- @return boolean\\nlocal function isOpen(modem)\\n    expect.expect(1, modem, \\\"string\\\", \\\"nil\\\")\\n    if modem then\\n        if peripheral.getType(modem) ~= \\\"modem\\\" then return false end\\n        return peripheral.call(modem, \\\"isOpen\\\", constants.CHANNEL)\\n    else\\n        return not not peripheral.find(\\\"modem\\\", isOpen)\\n    end\\nend\\n\\n--- Transmits a packet on all open modems.\\n--- @param side string\\n--- @param packet string\\n--- @return boolean\\nlocal function transmit(side, packet)\\n    return pcall(\\n        peripheral.call,\\n        side,\\n        \\\"transmit\\\",\\n        constants.CHANNEL,\\n        constants.CHANNEL,\\n        packet\\n    )\\nend\\n\\nreturn {\\n    open = open,\\n    close = close,\\n    isOpen = isOpen,\\n    transmit = transmit,\\n}\\n\",\"size\":1609,\"sha256\":\"ce72524e4dde7865c1a52130f93d89aad404e8b5e1697cca97cc899911964962\"},\"ecnet2/protocol.lua\":{\"content\":\"local class = require \\\"ecnet2.class\\\"\\nlocal expect = require \\\"cc.expect\\\"\\nlocal HandshakeState = require \\\"ecnet2.handshake_state\\\"\\nlocal addressEncoder = require \\\"ecnet2.address_encoder\\\"\\nlocal blake3 = require \\\"ccryptolib.blake3\\\"\\nlocal Connection = require \\\"ecnet2.connection\\\"\\nlocal Listener = require \\\"ecnet2.listener\\\"\\nlocal modems = require \\\"ecnet2.modems\\\"\\n\\n--- A namespace for interpreting messages received over connections.\\n--- @class ecnet2.Protocol\\n--- @field _interface ecnet2.IProtocol The underlying interface.\\n--- @field _identity ecnet2.Identity The protocol's identity.\\n--- @field _hash string The protocol name hash.\\nlocal Protocol = class \\\"ecnet2.Protocol\\\"\\n\\n--- The interface for describing a new protocol.\\n--- @class ecnet2.IProtocol\\n--- @field name string The protocol's name.\\n--- @field key string? A pre-shared key for protocol connections.\\n--- @field serialize fun(obj: any): string The serializer for messages.\\n--- @field deserialize fun(str: string): any The deserializer for messages.\\n\\n--- @param interface ecnet2.IProtocol\\n--- @param identity ecnet2.Identity The protocol's identity.\\nfunction Protocol:initialise(interface, identity)\\n    expect.field(interface, \\\"name\\\", \\\"string\\\")\\n    expect.field(interface, \\\"key\\\", \\\"string\\\", \\\"nil\\\")\\n    expect.field(interface, \\\"serialize\\\", \\\"function\\\")\\n    expect.field(interface, \\\"deserialize\\\", \\\"function\\\")\\n    if interface.key then\\n        assert(#interface.key == 32, \\\"invalid key size, must be 32 bytes\\\")\\n        self._hash = blake3.digestKeyed(interface.key, interface.name)\\n    else\\n        self._hash = blake3.digest(interface.name)\\n    end\\n    self._interface = interface\\n    self._identity = identity\\nend\\n\\n--- Creates a new connection using this protocol and a modem side.\\n--- @param address string The responder's address.\\n--- @param modem string The modem name to connect through.\\n--- @return ecnet2.Connection\\nfunction Protocol:connect(address, modem)\\n    expect.expect(1, address, \\\"string\\\")\\n    expect.expect(2, modem, \\\"string\\\")\\n    assert(modems.isOpen(modem), \\\"modem isn't open: \\\" .. modem)\\n    local rpk = assert(addressEncoder.parse(address), \\\"invalid address\\\")\\n    local psk = blake3.digest(rpk .. self._hash)\\n    local descriptor = blake3.digest(psk)\\n    local msk = self._identity._msk\\n    local lpk = self._identity._pk\\n    local state, data = HandshakeState.iA(msk, lpk, rpk, \\\"\\\", psk)\\n    modems.transmit(modem, descriptor .. data)\\n    return Connection(state, self, modem)\\nend\\n\\n--- Creates a listener for this protocol on all open modems.\\n--- @return ecnet2.Listener\\nfunction Protocol:listen()\\n    return Listener(self)\\nend\\n\\nreturn Protocol\\n\",\"size\":2617,\"sha256\":\"7a6a1ef196908098836e0657e9c813723eb335179b8891ca6b36a0eedaec351c\"},\"ecnet2/symmetric_state.lua\":{\"content\":\"local class = require \\\"ecnet2.class\\\"\\nlocal blake3 = require \\\"ccryptolib.blake3\\\"\\nlocal CipherState = require \\\"ecnet2.cipher_state\\\"\\n\\n--- A symmetric state containing keys and a handshake transcript hash.\\n--- @class ecnet2.SymmetricState\\n--- @field private h string The current handshake transcript hash.\\n--- @field private ck string The current chaining key for deriving other keys.\\n--- @field private cs ecnet2.CipherState The current encryption CipherState.\\nlocal SymmetricState = class \\\"ecnet2.SymmetricState\\\"\\n\\n-- We modify Noise so much that it's meaningless to use their naming standard.\\nlocal PROTOCOL_NAME = blake3.digest\\n    \\\"ecnet2 2023-01-03 04:16 UTC network handshake protocol\\\"\\n\\nlocal DESCRIPTOR_KDF = blake3.deriveKey\\n    \\\"ecnet2 2023-01-05 00:00 UTC handshake descriptor context\\\"\\n\\nfunction SymmetricState:initialise()\\n    self.h = PROTOCOL_NAME\\n    self.ck = PROTOCOL_NAME\\n    self.cs = CipherState(nil)\\nend\\n\\n--- Mixes keying material into the key and the transcript hash.\\n--- @param material string\\nfunction SymmetricState:mixKeyAndHash(material)\\n    local tk = blake3.digestKeyed(self.ck, material, 96)\\n    self.ck = tk:sub(1, 32)\\n    self:mixHash(tk:sub(33, 64))\\n    self.cs = CipherState(tk:sub(65))\\nend\\n\\n--- Mixes keying material into the key.\\n--- @param material string\\nfunction SymmetricState:mixKey(material)\\n    local tk = blake3.digestKeyed(self.ck, material, 64)\\n    self.ck = tk:sub(1, 32)\\n    self.cs = CipherState(tk:sub(33))\\nend\\n\\n--- Mixes data into the transcript hash.\\n--- @param data string\\n--- @return string data The input data.\\nfunction SymmetricState:mixHash(data)\\n    self.h = blake3.digest(self.h .. data)\\n    return data\\nend\\n\\n--- Returns the current transcript hash.\\n--- @return string hash The handshake transcript hash.\\nfunction SymmetricState:getHandshakeHash()\\n    return self.h\\nend\\n\\n--- Encrypts data and adds it to the transcript.\\n--- @param plaintext string The plaintext to encrypt.\\n--- @return string ciphertext The encrypted plaintext \\nfunction SymmetricState:encryptAndHash(plaintext)\\n    local ciphertext = self.cs:encryptWithAd(self.h, plaintext)\\n    return self:mixHash(ciphertext)\\nend\\n\\n--- Adds data to the transcript and tries to decrypt it.\\n--- @param ciphertext string The ciphertext to decrypt.\\n--- @return string? plaintext The decrypted plaintext, or nil on failure.\\nfunction SymmetricState:decryptAndHash(ciphertext)\\n    local plaintext = self.cs:decryptWithAd(self.h, ciphertext)\\n    self:mixHash(ciphertext)\\n    return plaintext\\nend\\n\\n--- Returns the current descriptor for the state.\\n--- @return string descriptor The descriptor.\\nfunction SymmetricState:descriptor()\\n    return DESCRIPTOR_KDF(self.ck .. self.h)\\nend\\n\\n--- Splits the state into two cipher states, finishing the handshake.\\n--- @return ecnet2.CipherState, ecnet2.CipherState\\nfunction SymmetricState:split()\\n    local tk = blake3.digestKeyed(self.ck, \\\"\\\", 64)\\n    return CipherState(tk:sub(1, 32)), CipherState(tk:sub(33))\\nend\\n\\nreturn SymmetricState\\n\",\"size\":2972,\"sha256\":\"07fa09045c9c8b8b97098ac845c347c8fe11b9827c2d34c5cfb1f4969e891153\"},\"ecnet2/uid.lua\":{\"content\":\"--- Unique string ID generator.\\n-- It's just a random string concatenated to a counter. IDs are unique but not\\n-- uniformly random. Multiple instances of the generator are safe to use and\\n-- will be unique in relation to each other.\\n\\nlocal random = require \\\"ccryptolib.random\\\"\\n\\nlocal counter, suffix\\n\\n--- Returns a unique ID\\n--- @return string uid A 32-byte unique string id.\\nreturn function()\\n    if not suffix or counter >= 2 ^ 32 then\\n        suffix = random.random(28)\\n        counter = 0\\n    end\\n    counter = counter + 1\\n    return (\\\"<I4\\\"):pack(counter) .. suffix\\nend\\n\",\"size\":574,\"sha256\":\"0193aed22b20ecc82390f791910378aeaac076c81b49765054a6049e2e460bd6\"},\"lock.json\":{\"content\":\"{\\n  \\\"ecnet\\\": \\\"15dcf3e6d396523f7273d67e00cc24e5bc52c8c8\\\",\\n  \\\"ccryptolib\\\": \\\"14489aa1ec408f8a07e1292be5ef91dfe8567c74\\\"\\n}\\n\",\"size\":118,\"sha256\":\"9486c256aec816894076579d2ef77648ac83701e167420a0f0d1d3c523590751\"},\"templates/directory/index.json\":{\"content\":\"{\\n  \\\"version\\\": 1,\\n  \\\"title\\\": \\\"Favorite corners\\\",\\n  \\\"children\\\": [\\n    {\\n      \\\"type\\\": \\\"heading\\\",\\n      \\\"text\\\": \\\"WORLDS WORTH FINDING\\\"\\n    },\\n    {\\n      \\\"type\\\": \\\"paragraph\\\",\\n      \\\"text\\\": \\\"A curated directory. Replace these links with your favorites.\\\"\\n    },\\n    {\\n      \\\"type\\\": \\\"link\\\",\\n      \\\"text\\\": \\\"Lantern welcome\\\",\\n      \\\"href\\\": \\\"hub://lantern/\\\"\\n    },\\n    {\\n      \\\"type\\\": \\\"link\\\",\\n      \\\"text\\\": \\\"The Observatory\\\",\\n      \\\"href\\\": \\\"hub://showcase/\\\"\\n    },\\n    {\\n      \\\"type\\\": \\\"link\\\",\\n      \\\"text\\\": \\\"Help and reference\\\",\\n      \\\"href\\\": \\\"hub://help/\\\"\\n    }\\n  ]\\n}\\n\",\"size\":561,\"sha256\":\"0c97e10fb8f4857768ce6ae1498961d9b3dcf1ebad5bf37db65d4a8568d3a514\"},\"templates/documentation/index.json\":{\"content\":\"{\\n  \\\"version\\\": 1,\\n  \\\"title\\\": \\\"Project handbook\\\",\\n  \\\"children\\\": [\\n    {\\n      \\\"type\\\": \\\"heading\\\",\\n      \\\"text\\\": \\\"YOUR PROJECT / HANDBOOK\\\"\\n    },\\n    {\\n      \\\"type\\\": \\\"paragraph\\\",\\n      \\\"text\\\": \\\"A small, readable guide for your community.\\\"\\n    },\\n    {\\n      \\\"type\\\": \\\"link\\\",\\n      \\\"text\\\": \\\"Getting started\\\",\\n      \\\"href\\\": \\\"/start.json\\\"\\n    },\\n    {\\n      \\\"type\\\": \\\"link\\\",\\n      \\\"text\\\": \\\"Visit Lantern help\\\",\\n      \\\"href\\\": \\\"hub://help/\\\"\\n    }\\n  ]\\n}\\n\",\"size\":443,\"sha256\":\"7744aab78a6dca6285f12aad002982ae705d68afd55cd89d862132feb62a0ff6\"},\"templates/documentation/start.json\":{\"content\":\"{\\n  \\\"version\\\": 1,\\n  \\\"title\\\": \\\"Getting started\\\",\\n  \\\"children\\\": [\\n    {\\n      \\\"type\\\": \\\"heading\\\",\\n      \\\"text\\\": \\\"START HERE\\\"\\n    },\\n    {\\n      \\\"type\\\": \\\"list\\\",\\n      \\\"items\\\": [\\n        \\\"Describe what readers need.\\\",\\n        \\\"Give them one action at a time.\\\",\\n        \\\"Show what success looks like.\\\"\\n      ]\\n    },\\n    {\\n      \\\"type\\\": \\\"link\\\",\\n      \\\"text\\\": \\\"Back to handbook\\\",\\n      \\\"href\\\": \\\"/\\\"\\n    }\\n  ]\\n}\\n\",\"size\":403,\"sha256\":\"c25751caff8466d181e32e1c817ee42d3313a16ced102ec6b46c5d105f0ad950\"},\"templates/fieldnotes/first.json\":{\"content\":\"{\\n  \\\"version\\\": 1,\\n  \\\"title\\\": \\\"First light\\\",\\n  \\\"children\\\": [\\n    {\\n      \\\"type\\\": \\\"heading\\\",\\n      \\\"text\\\": \\\"01 / FIRST LIGHT\\\"\\n    },\\n    {\\n      \\\"type\\\": \\\"paragraph\\\",\\n      \\\"text\\\": \\\"Today the workshop opened its doors. Replace this with your first story.\\\"\\n    },\\n    {\\n      \\\"type\\\": \\\"list\\\",\\n      \\\"items\\\": [\\n        \\\"What you built\\\",\\n        \\\"What you learned\\\",\\n        \\\"What comes next\\\"\\n      ]\\n    },\\n    {\\n      \\\"type\\\": \\\"link\\\",\\n      \\\"text\\\": \\\"Back to the journal\\\",\\n      \\\"href\\\": \\\"/\\\"\\n    }\\n  ]\\n}\\n\",\"size\":495,\"sha256\":\"586ca358b13e83846b810c580838001427e66b764b3600d48d37635814a45e14\"},\"templates/fieldnotes/index.json\":{\"content\":\"{\\n  \\\"version\\\": 1,\\n  \\\"title\\\": \\\"Field notes\\\",\\n  \\\"children\\\": [\\n    {\\n      \\\"type\\\": \\\"image\\\",\\n      \\\"text\\\": \\\"Sunrise over the mountains\\\",\\n      \\\"rows\\\": [\\n        \\\"ffffffff44444444ffffffff\\\",\\n        \\\"fffffff4441111444fffffff\\\",\\n        \\\"fffffffff111111fffffffff\\\",\\n        \\\"fffffff7777777777fffffff\\\",\\n        \\\"ffff777788887777777fffff\\\",\\n        \\\"ff7777888888887777777fff\\\"\\n      ]\\n    },\\n    {\\n      \\\"type\\\": \\\"heading\\\",\\n      \\\"text\\\": \\\"THE FIELD JOURNAL\\\"\\n    },\\n    {\\n      \\\"type\\\": \\\"paragraph\\\",\\n      \\\"text\\\": \\\"Dispatches from a world worth exploring.\\\"\\n    },\\n    {\\n      \\\"type\\\": \\\"link\\\",\\n      \\\"text\\\": \\\"Read the first dispatch\\\",\\n      \\\"href\\\": \\\"/first.json\\\"\\n    },\\n    {\\n      \\\"type\\\": \\\"link\\\",\\n      \\\"text\\\": \\\"Return to the Hub\\\",\\n      \\\"href\\\": \\\"hub://directory/\\\"\\n    }\\n  ]\\n}\\n\",\"size\":760,\"sha256\":\"cf5fe229ad82867e9746f2921e5545d7db26c9456ad02141091b212eff9350f9\"},\"templates/guestbook/app.lua\":{\"content\":\"return function(ctx)\\n  local P=ctx.page\\n  local entries=ctx.read('entries',{})\\n  if ctx.method=='POST' and ctx.path=='/sign' then\\n    local text=tostring(ctx.fields.message or ''):sub(1,240)\\n    if text:match('%S') then\\n      entries[#entries+1]={text=text,id=ctx.requestId}\\n      while #entries>30 do table.remove(entries,1) end\\n      ctx.write('entries',entries)\\n    end\\n    return {kind='redirect',path='/'}\\n  end\\n  if ctx.path~='/' then return nil end\\n  local children={P.heading{text='Leave a little warmth'},P.paragraph{text='A guestbook for passing travelers. Notes are public.'}}\\n  for _,entry in ipairs(entries) do children[#children+1]=P.paragraph{text=entry.text} end\\n  children[#children+1]=P.form{action='/sign',children={P.input{name='message',text='Your note'},P.submit{text='Sign guestbook'}}}\\n  return {kind='page',page=P.document('Guestbook',children)}\\nend\\n\",\"size\":875,\"sha256\":\"e8834fe50cbc8d80da76e19ffc5b617721454dfe6c61b5530030f2f47732259e\"},\"templates/guestbook/index.json\":{\"content\":\"{\\n  \\\"version\\\": 1,\\n  \\\"title\\\": \\\"Guestbook\\\",\\n  \\\"children\\\": [\\n    {\\n      \\\"type\\\": \\\"heading\\\",\\n      \\\"text\\\": \\\"Guestbook\\\"\\n    },\\n    {\\n      \\\"type\\\": \\\"paragraph\\\",\\n      \\\"text\\\": \\\"Leave a note for the next traveler.\\\"\\n    },\\n    {\\n      \\\"type\\\": \\\"columns\\\",\\n      \\\"children\\\": [\\n        {\\n          \\\"type\\\": \\\"section\\\",\\n          \\\"children\\\": [\\n            {\\n              \\\"type\\\": \\\"heading\\\",\\n              \\\"text\\\": \\\"Explore\\\"\\n            },\\n            {\\n              \\\"type\\\": \\\"paragraph\\\",\\n              \\\"text\\\": \\\"Built for our world.\\\"\\n            }\\n          ]\\n        },\\n        {\\n          \\\"type\\\": \\\"section\\\",\\n          \\\"children\\\": [\\n            {\\n              \\\"type\\\": \\\"heading\\\",\\n              \\\"text\\\": \\\"Make it yours\\\"\\n            },\\n            {\\n              \\\"type\\\": \\\"paragraph\\\",\\n              \\\"text\\\": \\\"Edit this page with Lantern.\\\"\\n            }\\n          ]\\n        }\\n      ]\\n    },\\n    {\\n      \\\"type\\\": \\\"link\\\",\\n      \\\"text\\\": \\\"Back to Lantern\\\",\\n      \\\"href\\\": \\\"ln://home\\\"\\n    }\\n  ]\\n}\\n\",\"size\":968,\"sha256\":\"c4c1ec08cea6e741d7ea44df2cde8005248bb56ec78faeb17a7f0415fd5a5725\"},\"templates/personal/index.json\":{\"content\":\"{\\n  \\\"version\\\": 1,\\n  \\\"title\\\": \\\"A corner of my world\\\",\\n  \\\"children\\\": [\\n    {\\n      \\\"type\\\": \\\"image\\\",\\n      \\\"text\\\": \\\"Sunrise over the mountains\\\",\\n      \\\"rows\\\": [\\n        \\\"ffffffff44444444ffffffff\\\",\\n        \\\"fffffff4441111444fffffff\\\",\\n        \\\"fffffffff111111fffffffff\\\",\\n        \\\"fffffff7777777777fffffff\\\",\\n        \\\"ffff777788887777777fffff\\\",\\n        \\\"ff7777888888887777777fff\\\"\\n      ]\\n    },\\n    {\\n      \\\"type\\\": \\\"heading\\\",\\n      \\\"text\\\": \\\"A CORNER OF MY WORLD\\\"\\n    },\\n    {\\n      \\\"type\\\": \\\"paragraph\\\",\\n      \\\"text\\\": \\\"A build journal, a few discoveries, and a place to call home.\\\"\\n    },\\n    {\\n      \\\"type\\\": \\\"columns\\\",\\n      \\\"children\\\": [\\n        {\\n          \\\"type\\\": \\\"section\\\",\\n          \\\"children\\\": [\\n            {\\n              \\\"type\\\": \\\"heading\\\",\\n              \\\"text\\\": \\\"ABOUT\\\"\\n            },\\n            {\\n              \\\"type\\\": \\\"paragraph\\\",\\n              \\\"text\\\": \\\"Replace this text with your story.\\\"\\n            }\\n          ]\\n        },\\n        {\\n          \\\"type\\\": \\\"section\\\",\\n          \\\"children\\\": [\\n            {\\n              \\\"type\\\": \\\"heading\\\",\\n              \\\"text\\\": \\\"PROJECTS\\\"\\n            },\\n            {\\n              \\\"type\\\": \\\"list\\\",\\n              \\\"items\\\": [\\n                \\\"A mountainside workshop\\\",\\n                \\\"A railway to somewhere new\\\",\\n                \\\"Your next great idea\\\"\\n              ]\\n            }\\n          ]\\n        }\\n      ]\\n    },\\n    {\\n      \\\"type\\\": \\\"link\\\",\\n      \\\"text\\\": \\\"Find other worlds\\\",\\n      \\\"href\\\": \\\"hub://directory/\\\"\\n    }\\n  ]\\n}\\n\",\"size\":1462,\"sha256\":\"2bf4b857ca4f565263d8c57b25899f6e9bb9830b29b323806f07bed3140bdedf\"},\"templates/showcase/app.lua\":{\"content\":\"return function(ctx)\\n  local P=ctx.page\\n  local art={'ffffffffffff1111ffffffffffff','fffffffffff14441fffffffffff','ffffffffff1444441ffffffffff','fffffffffff14441fffffffffff','ffffffffff1111111ffffffffff','ffffffffff1fffff1ffffffffff','fffffffff11fffff11fffffffff','ffffffff11111111111ffffffff'}\\n  local children={P.image{rows=art,text='A lantern in the night'},P.heading{text='THE LANTERN OBSERVATORY'},P.paragraph{text='Small screens. Bright ideas. An interactive field station between worlds.'},P.columns{children={P.section{children={P.heading{text='01 / EXPLORE'},P.paragraph{text='Palette pixel art, headings, lists and links.'}}},P.section{children={P.heading{text='02 / INTERACT'},P.paragraph{text='Forms travel securely to server-side Lua.'}}}}}}\\n  if ctx.method=='POST' and ctx.path=='/experiment' then\\n    local code=tostring(ctx.fields.code or '')\\n    if code~='glow' then\\n      children[#children+1]=P.heading{text='Try the demo code: glow'}\\n      children[#children+1]=P.paragraph{text='That code did not match. No password or form content was saved.'}\\n    else\\n      local name=tostring(ctx.fields.explorer or 'Traveler'):sub(1,60)\\n      local biome=tostring(ctx.fields.biome or 'Forest'):sub(1,40)\\n      children[#children+1]=P.heading{text='Welcome, '..name..'.'}\\n      children[#children+1]=P.paragraph{text='Your destination: '..biome..'. Your beacon is '..(ctx.fields.beacon and 'lit' or 'off')..'. This response was built by server Lua.'}\\n      children[#children+1]=P.paragraph{text='The demo code was checked and discarded. No login session was created.'}\\n    end\\n    children[#children+1]=P.link{text='Try another experiment',href='/'}\\n  elseif ctx.method=='GET' and ctx.path=='/' then\\n    children[#children+1]=P.list{items={'Layouts stack on pocket computers.','Keyboard: Tab selects, Enter activates.','Password fields are masked in the updated browser.'}}\\n    children[#children+1]=P.heading{text='Run your own experiment'}\\n    children[#children+1]=P.paragraph{text='Use the public demo code glow. Never enter a real password here. Masking hides typing; it is not account authentication.'}\\n    children[#children+1]=P.form{action='/experiment',children={P.input{name='explorer',text='Explorer name'},P.select{name='biome',text='Choose a biome',options={'Forest','Desert','Snowy peaks','The End'}},P.checkbox{name='beacon',text='Light the beacon'},P.input{name='code',text='Demo code (glow)',password=true},P.submit{text='Launch experiment'}}}\\n  else return {kind='error',status=404,message='Page not found'} end\\n  children[#children+1]=P.link{text='Read the component reference',href='hub://help/components.json'}\\n  return {kind='page',page=P.document('Lantern Observatory',children)}\\nend\\n\",\"size\":2717,\"sha256\":\"66af3d25c72f520ff0ae1d50a228cee71da941b86211d333b4da8625d80d7775\"},\"update-key.txt\":{\"content\":\"c2ea2e4f1f08cc3c712728e5685dc5220dad0dc0c1f06cc5d7756aa25caa1008\",\"size\":64,\"sha256\":\"dee287cf16ba249fcf95482136a56f6d15c89a04001c945de22deec567367959\"}}}","signature":"30b88a05eaa9e4635cae14172d9ee7704640ab4ca0abb35f8a8cd27972a63e24d9e475a17ace70549022ec4bf6b8495017adce3a640da5bb348076f279f5440a"}]=])
local U=require('lantern.util')
local UI=require('lantern.ui')
local installerArgs={...}
local unattended=installerArgs[1]=='--yes'
local function fetch(url)
  assert(http,'HTTP is disabled; use the offline installer')
  local h,err=http.get(url,nil,true); assert(h,err or 'Download failed')
  local chunks,total={},0
  while true do local part=h.read(8192); if not part then break end; total=total+#part; if total>1500000 then h.close(); error('Download too large') end; chunks[#chunks+1]=part end
  h.close(); return table.concat(chunks)
end
local stages={'Check computer','Load release','Verify signature','Install files','Ready to launch'}
local function screen(title,detail,step,done,total)
  local s=UI.new(); s:header('SETUP / '..title)
  local compact=s.h<17
  for i,label in ipairs(stages) do
    local mark=i<step and '[OK]' or i==step and '[>>]' or '[  ]'
    s:text(2,(compact and 4 or 5)+i,mark..' '..label,i==step and 'accent' or i<step and 'text' or 'muted')
  end
  local y=math.min(s.h-4,compact and 10 or 12)
  s:text(2,y,detail,'text')
  if total then
    local width=math.max(1,s.w-4);local filled=math.floor(width*done/total)
    s:text(2,y+1,string.rep('=',filled)..string.rep('-',width-filled),'accent')
    s:text(2,y+2,done..' / '..total..' files','muted')
  end
  s:footer('Your sites and identity stay yours.');s:flush();sleep(0)
end
local function welcome()
  local selected=1
  local options={'Browser','Browser + hosting','Hosting tools'}
  local notes={'Explore the Hub and nearby sites.','Browse, create and host your own sites.','Create and serve sites in your world.'}
  while true do
    local s=UI.new();s:header('A little light. A bigger world.')
    s:text(2,4,'Choose your setup','muted')
    for i,label in ipairs(options) do
      local y=5+(i-1)*3;s:bar(y,i==selected and 'panel' or 'bg')
      s:text(2,y,(i==selected and '> ' or '  ')..label,i==selected and 'accent' or 'text',i==selected and 'panel' or 'bg')
      s:text(4,y+1,notes[i],'muted')
    end
    s:footer('Click / Enter: install   Q: cancel');s:flush()
    local e,k,x,y=os.pullEvent()
    if e=='key' then
      if k==keys.up then selected=math.max(1,selected-1) elseif k==keys.down then selected=math.min(3,selected+1)
      elseif k==keys.enter then return ({1,3,2})[selected] elseif k==keys.q or k==keys.escape then return nil end
    elseif e=='mouse_click' then
      for i=1,3 do if y>=5+(i-1)*3 and y<=6+(i-1)*3 then return ({1,3,2})[i] end end
    end
  end
end
local function run()
  local profile=unattended and 3 or welcome()
  if not profile then return end
  local function progress(message,step) screen(message,message,step) end
  progress('Checking this computer',1)
  assert(bit32 and textutils.unserializeJSON and term.blit,'CC:Tweaked with JSON, bit32 and terminal blit is required')
  local envelope=OFFLINE_ENVELOPE
  progress(envelope and 'Offline bundle ready' or 'Connecting to release source',2)
  if not envelope then
    progress('Downloading release',2,5)
    local ok,s=pcall(fetch,GITHUB_URL)
    if ok then envelope=assert(textutils.unserializeJSON(s)) else
      assert(#PASTEBIN_PARTS>0,'GitHub unavailable and no Pastebin mirror configured: '..tostring(s))
      local parts={}; for _,id in ipairs(PASTEBIN_PARTS) do parts[#parts+1]=fetch('https://pastebin.com/raw/'..id) end
      envelope=assert(textutils.unserializeJSON(table.concat(parts)))
    end
  end
  progress('Verifying signed release',3,5)
  local release,total=require('lantern.release').verify(envelope,PUBLIC_KEY)
  local free=fs.getFreeSpace('/'); assert(type(free)~='number' or free>total+65536,'Not enough free space to stage this release')
  local root='/lantern'; fs.makeDir(root)
  if fs.exists(root..'/transaction') then
    if fs.exists(root..'/previous') then if fs.exists(root..'/current') then fs.delete(root..'/current') end; fs.move(root..'/previous',root..'/current') end
    fs.delete(root..'/transaction')
  end
  local stage=root..'/stage'; if fs.exists(stage) then fs.delete(stage) end
  progress('Staging application files',4,5)
  local paths={};for path in pairs(release.files) do paths[#paths+1]=path end;table.sort(paths)
  for i,path in ipairs(paths) do
    local file=release.files[path];U.write(stage..'/'..path,file.content)
    assert(U.read(stage..'/'..path,262144)==file.content,'Staged file verification failed')
    if i%4==0 or i==#paths then screen('Installing Lantern',path,4,i,#paths) end
  end
  U.write(stage..'/release.json',textutils.serializeJSON({version=release.version,sequence=release.sequence or 0,profile=profile}))
  if fs.exists(root..'/previous') then fs.delete(root..'/previous') end
  U.write(root..'/transaction','pending')
  if fs.exists(root..'/current') then fs.move(root..'/current',root..'/previous') end
  fs.move(stage,root..'/current')
  U.atomic('/lantern.lua',release.files['launcher.lua'].content)
  fs.delete(root..'/transaction')
  progress('Installed '..release.version,5,5)
  local startup=unattended and 1 or UI.choose('Startup preference',{'Launch manually (recommended)',profile==2 and 'Open server tools at startup' or 'Launch browser at startup'})
  if startup==2 then
    if fs.exists('/startup') and not fs.isDir('/startup') then UI.prompt('Existing startup file preserved. Configure startup manually. Enter to continue.')
    elseif fs.exists('/startup/lantern.lua') then UI.prompt('Existing startup entry preserved. Enter to continue.')
    else U.write('/startup/lantern.lua','-- Lantern managed startup\nshell.run("/lantern.lua")\n') end
  end
  if not unattended then
    local choices=profile==2 and {'Open server tools','Create a local site','Finish setup'} or {'Open Lantern Hub','Open local home','Finish setup'}
    local nextStep=UI.choose('Lantern is ready. Make yourself at home.',choices)
    if profile==2 then
      if nextStep==1 then shell.run('/lantern.lua','host') elseif nextStep==2 then shell.run('/lantern.lua','edit') end
    elseif nextStep==1 then shell.run('/lantern.lua','hub://directory/') elseif nextStep==2 then shell.run('/lantern.lua') end
  end
end
UI.restore(run)
