diff --git a/onchain/permissionless-arbitration/offchain/.luarc.json b/onchain/permissionless-arbitration/offchain/.luarc.json new file mode 100644 index 00000000..5f8d0ef7 --- /dev/null +++ b/onchain/permissionless-arbitration/offchain/.luarc.json @@ -0,0 +1,13 @@ +{ + "$schema": "https://raw.githubusercontent.com/sumneko/vscode-lua/master/setting/schema.json", + + "runtime.version": "Lua 5.4", + + "diagnostics": { + "enable": true + }, + + "workspace.library": { + "runtime/lua": true + } +} diff --git a/onchain/permissionless-arbitration/offchain/cryptography/hash.lua b/onchain/permissionless-arbitration/offchain/cryptography/hash.lua new file mode 100644 index 00000000..b05df9f1 --- /dev/null +++ b/onchain/permissionless-arbitration/offchain/cryptography/hash.lua @@ -0,0 +1,114 @@ +-- local keccak_bin = require "cartesi".keccak + +local function hex_from_bin(bin) + assert(bin:len() == 32) + return "0x" .. string.gsub(bin, ".", function(c) + return string.format("%02x", string.byte(c)) + end) +end + +-- local function keccak(...) +-- return hex_from_bin(keccak_bin(...)) +-- end + +local function keccak(...) + local args = {...} + assert(#args > 0) + for _,v in ipairs(args) do + assert(v:len() == 66, "string not a hash: " .. v) + end + local args_str = table.concat(args, " ") + + + local cmd = string.format( + [[cast keccak $(cast --concat-hex %s)]], + args_str + ) + + local handle = io.popen(cmd) + assert(handle) + + local digest = handle:read() + handle:close() + return digest +end + +local internalized_hahes = {} +local iterateds = {} + +local Hash = {} +Hash.__index = Hash + +function Hash:from_digest(digest_hex) + assert(type(digest_hex) == "string", digest_hex:len() == 66) + + local x = internalized_hahes[digest_hex] + if x then return x end + + local h = {digest_hex = digest_hex} + iterateds[h] = {h} + setmetatable(h, self) + internalized_hahes[digest_hex] = h + return h +end + +function Hash:from_digest_bin(digest_bin) + local digest_hex = hex_from_bin(digest_bin) + return self:from_digest(digest_hex) +end + +function Hash:from_data(data) + local digest_hex = keccak(data) + return self:from_digest(digest_hex) +end + +function Hash:join(other_hash) + assert(getmetatable(other_hash) == Hash) + local digest_hex = keccak(self.digest_hex, other_hash.digest_hex) + local ret = Hash:from_digest(digest_hex) + ret.left = self.digest_hex + ret.right = other_hash.digest_hex + return ret +end + +function Hash:children() + local left, right= self.left, self.right + if left and right then + return true, left, right + else + return false + end +end + +function Hash:iterated_merkle(level) + level = level + 1 + local iterated = iterateds[self] + + local ret = iterated[level] + if ret then return ret end + + local i = #iterated -- at least 1 + local highest_level = iterated[i] + while i < level do + highest_level = highest_level:join(highest_level) + i = i + 1 + iterated[i] = highest_level + end + + return highest_level +end + +Hash.__tostring = function (x) + return x.digest_hex +end + +local zero_bytes32 = "0x0000000000000000000000000000000000000000000000000000000000000000" +local zero_hash = Hash:from_digest(zero_bytes32) + +Hash.zero = zero_hash + +function Hash:is_zero() + return self == zero_hash +end + +return Hash diff --git a/onchain/permissionless-arbitration/offchain/cryptography/merkle.lua b/onchain/permissionless-arbitration/offchain/cryptography/merkle.lua new file mode 100644 index 00000000..8fabcaf4 --- /dev/null +++ b/onchain/permissionless-arbitration/offchain/cryptography/merkle.lua @@ -0,0 +1,144 @@ +local function ulte(a, b) + return a == b or math.ult(a, b) +end + +local function is_pow2(x) + return x & (x-1) == 0 +end + +-- Returns number of leading zeroes of x. Shamelessly stolen from the book +-- Hacker's Delight. +local function clz(x) + if x == 0 then return 64 end + local n = 0 + if (x & 0xFFFFFFFF00000000) == 0 then n = n + 32; x = x << 32 end + if (x & 0xFFFF000000000000) == 0 then n = n + 16; x = x << 16 end + if (x & 0xFF00000000000000) == 0 then n = n + 8; x = x << 8 end + if (x & 0xF000000000000000) == 0 then n = n + 4; x = x << 4 end + if (x & 0xC000000000000000) == 0 then n = n + 2; x = x << 2 end + if (x & 0x8000000000000000) == 0 then n = n + 1 end + return n +end + +-- Returns number of trailing zeroes of x. Shamelessly stolen from the book +-- Hacker's Delight. +local function ctz(x) + x = x & (~x + 1) + return 63 - clz(x) +end + + +local Slice = {} +Slice.__index = Slice + +function Slice:new(arr, start_idx_inc, end_idx_ex) + start_idx_inc = start_idx_inc or 1 + end_idx_ex = end_idx_ex or #arr + 1 + assert(start_idx_inc > 0) + assert(ulte(start_idx_inc, end_idx_ex)) + assert(end_idx_ex <= #arr + 1) + local s = { + arr = arr, + start_idx_inc = start_idx_inc, + end_idx_ex = end_idx_ex, + } + setmetatable(s, self) + return s +end + +function Slice:slice(si, ei) + assert(si > 0) + assert(ulte(si, ei)) + local start_idx_inc = self.start_idx_inc + si - 1 + local end_idx_ex = self.start_idx_inc + ei - 1 + assert(ulte(end_idx_ex, self.end_idx_ex)) + return Slice:new(self.arr, start_idx_inc, end_idx_ex) +end + +function Slice:len() + return self.end_idx_ex - self.start_idx_inc +end + +function Slice:get(idx) + idx = assert(math.tointeger(idx)) + assert(idx > 0) + local i = self.start_idx_inc + idx - 1 + assert(i < self.end_idx_ex) + return self.arr[i] +end + +local function semi_sum(a, b) + assert(ulte(a, b)) + return a + (b - a) // 2 +end + +function Slice:find_cell_containing(elem) + local l, r = 1, self:len() + while l < r do + local m = semi_sum(l, r) + if self:get(m).accumulated_count < elem then + l = m + 1 + else + r = m + end + end + if l == self:len() then + assert(elem <= self:get(l).accumulated_count + 1) + end + return l +end + + +local MerkleBuilder = {} +MerkleBuilder.__index = MerkleBuilder + +function MerkleBuilder:new() + local m = { + leafs = {} + } + setmetatable(m, self) + return m +end + +function MerkleBuilder:add(hash, rep) + rep = rep or 1 + + local last_accumulated_count + if self.leafs[#self.leafs] then + last_accumulated_count = self.leafs[#self.leafs].accumulated_count + else + last_accumulated_count = 0 + end + + local accumulated_count = rep + last_accumulated_count + + table.insert(self.leafs, {hash = hash, accumulated_count = accumulated_count}) +end + +local function merkle(leafs, log2size, stride) + local first_time = stride * (1 << log2size) + 1 + local last_time = (stride + 1) * (1 << log2size) + + local first_cell = leafs:find_cell_containing(first_time) + local last_cell = leafs:find_cell_containing(last_time) + + if first_cell == last_cell then + return leafs:get(first_cell).hash:iterated_merkle(log2size) + end + + local slice = leafs:slice(first_cell, last_cell + 1) + local hash_left = merkle(slice, log2size - 1, 2 * stride) + local hash_right = merkle(slice, log2size - 1, 2 * stride + 1) + + return hash_left:join(hash_right) +end + +function MerkleBuilder:build() + local last = assert(self.leafs[#self.leafs]) + local count = last.accumulated_count + assert(is_pow2(count)) + local log2size = ctz(count) + return merkle(Slice:new(self.leafs), log2size, 0) +end + +return MerkleBuilder