Skip to content

Commit 08492a0

Browse files
JuliusK9matteo-cristinonsuzzi
authored
Merkle tree tests (#1049)
* test: new test file .lua for Merkle tree * statement: creation of new when statement 'create merkle root' * modified when statement * new statements and tests for merkle tree * new statements: now you can choose the hash * feat: add of a new function and correction of the statements * refactor: avoid more code duplication add also license header to zencode_merkle.lua * added function to verify integrity of a merkle tree and two 'when' verify statements. The dictionary path works only if the dictionary is an array * separation of error messages in verify_merkle_tree and substituted zencod_assert with error message * added a function that allows to create a merkle tree ( if the data table has power of 2 elements and modified the function that allows to create only the merkle root * adding the generalize function for the creation of a merkle tree according to Frigo's RFC --------- Co-authored-by: matteo-cristino <[email protected]> Co-authored-by: nsuzzi <[email protected]>
1 parent 09112b6 commit 08492a0

9 files changed

Lines changed: 322 additions & 0 deletions

File tree

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"data": [
3+
"data1",
4+
"data2",
5+
"data3",
6+
"data4"
7+
]
8+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"merkle_root":"1Fu3eBfOGVlDihcanmfcZj45yuy4Z3/SrSq1iupzD/Q="}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
Scenario 'merkle': create merkle root
2+
Given I have a 'string array' named 'data'
3+
4+
When I create the merkle root of 'data'
5+
6+
Then print the 'merkle root'
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"data": {
3+
"data1": [
4+
"data1",
5+
"data2",
6+
"data3",
7+
"data4"
8+
],
9+
"data2":"data2"
10+
}
11+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"merkle_root":"1Fu3eBfOGVlDihcanmfcZj45yuy4Z3/SrSq1iupzD/Q="}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
Scenario 'merkle': create merkle root
2+
Given I have a 'string dictionary' named 'data'
3+
4+
When I create the merkle root of dictionary path 'data.data1'
5+
6+
Then print the 'merkle root'

src/lua/zencode_merkle.lua

Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
--[[
2+
--This file is part of zenroom
3+
--
4+
--Copyright (C) 2018-2025 Dyne.org foundation
5+
--designed, written and maintained by Denis Roio <[email protected]>
6+
--
7+
--This program is free software: you can redistribute it and/or modify
8+
--it under the terms of the GNU Affero General Public License v3.0
9+
--
10+
--This program is distributed in the hope that it will be useful,
11+
--but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
--MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
--GNU Affero General Public License for more details.
14+
--
15+
--Along with this program you should have received a copy of the
16+
--GNU Affero General Public License v3.0
17+
--If not, see http://www.gnu.org/licenses/agpl.txt
18+
--
19+
--Last modified by Matteo Cristino
20+
--on Tuesday, 8th April 2025
21+
--]]
22+
23+
local function _hash(data, hashtype)
24+
hashtype = hashtype or CONF.hash
25+
--default hashtype: sha256
26+
--possible hashtype:
27+
--sha512
28+
--sha3_256
29+
--sha3_512
30+
--shake256
31+
--keccak256
32+
local _hf <const> = HASH:init(hashtype)
33+
return _hf:process(data)
34+
end
35+
36+
-- Function to create a Merkle root from a table of data
37+
local function _create_merkle_root(data_table, hashtype)
38+
local tree = {}
39+
40+
-- Hash each piece of data and add to the tree
41+
for _, data in ipairs(data_table) do
42+
table.insert(tree, _hash(data, hashtype))
43+
end
44+
45+
-- Build the tree by hashing pairs of nodes until a single hash (the root) is obtained
46+
while #tree > 1 do
47+
local temp_tree = {}
48+
for i = 1, #tree, 2 do
49+
if i + 1 <= #tree then
50+
local concatenated_hashes = tree[i] .. tree[i + 1]
51+
table.insert(temp_tree, _hash(concatenated_hashes, hashtype))
52+
else
53+
table.insert(temp_tree, tree[i])
54+
end
55+
end
56+
tree = temp_tree
57+
end
58+
59+
return tree[1] -- The Merkle root
60+
end
61+
62+
local function _zencode_merkle_root(name, hashtype)
63+
local data = pick_from_path(name, true)
64+
if not data or type(data) ~= 'table' then
65+
error("Table not found in path: "..name, 2)
66+
end
67+
ACK.merkle_root = _create_merkle_root(data, hashtype)
68+
return new_codec('merkle root', {zentype = "string"})
69+
end
70+
71+
When("create merkle root of ''", _zencode_merkle_root)
72+
When("create merkle root of '' using hash ''", _zencode_merkle_root)
73+
When("create merkle root of dictionary path ''", _zencode_merkle_root)
74+
When("create merkle root of dictionary path '' using hash ''", _zencode_merkle_root)
75+
76+
-- Function to verify the integrity of a Merkle root
77+
local function _verify_merkle_root(root, name)
78+
79+
local data_table = pick_from_path(name, true)
80+
local merkle_root = ACK[root]
81+
82+
if not data_table or not merkle_root then
83+
error("Inserted values should be not nill", 2)
84+
end
85+
86+
if type(data_table) ~= 'table' then
87+
error("Table not found in path: "..name, 2)
88+
end
89+
90+
local computed_root = _create_merkle_root(data_table)
91+
92+
if computed_root ~= merkle_root then
93+
error("Verification fail: elements are not equal", 2)
94+
end
95+
96+
return computed_root == merkle_root
97+
end
98+
99+
When("verify merkle root '' of ''", _verify_merkle_root)
100+
When("verify merkle root '' of dictionary path ''", _verify_merkle_root)
101+
102+
103+
104+
-- Function to create a Merkle tree from a table of data
105+
local function _create_merkle_tree(data_table, hashtype)
106+
local tree = {}
107+
108+
-- Hash each piece of data and add to the tree (tree basis leafs)
109+
for _, data in ipairs(data_table) do
110+
table.insert(tree, _hash(data, hashtype))
111+
end
112+
113+
local temp_tree = tree
114+
115+
-- Build the tree by hashing pairs of nodes until a single hash (the root) is obtained
116+
while #temp_tree > 1 do
117+
local temp_tree_step = {}
118+
for i = 1, #temp_tree, 2 do
119+
if i + 1 <= #temp_tree then
120+
local concatenated_hashes = temp_tree[i] .. temp_tree[i + 1]
121+
table.insert(tree, _hash(concatenated_hashes, hashtype))
122+
table.insert(temp_tree_step, _hash(concatenated_hashes, hashtype) )
123+
else
124+
table.insert(tree, temp_tree[i])
125+
table.insert(temp_tree_step, temp_tree[i])
126+
end
127+
end
128+
temp_tree = temp_tree_step
129+
end
130+
131+
return tree -- The merkle tree
132+
133+
end
134+
135+
136+
local function _create_merkle_tree2(data_table, hashtype)
137+
local N = #data_table
138+
--creation of the empty tree
139+
local tree = {}
140+
for i = 1, 2*N do
141+
tree[i] = "0"
142+
end
143+
144+
--hashing the data input a filling the end of the tree
145+
for i = N + 1, 2*N do
146+
tree[i] = _hash(data_table[i - N], hashtype)
147+
end
148+
149+
--filling the vector tree: the node in position i has as leafs the nodes in position 2i and 2i+1
150+
for i = N, 2, -1 do
151+
local concatenated = tree[2*i - 1] .. tree[2*i]
152+
tree[i] = _hash(concatenated, hashtype)
153+
end
154+
155+
return tree
156+
157+
end

test/lua/merkle_tree.lua

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
print "Merkle tree test"
2+
3+
local function hash(data)
4+
return sha256(data)
5+
end
6+
7+
-- Function to create a Merkle tree from a table of data
8+
local function create_merkle_tree(data_table)
9+
local tree = {}
10+
11+
-- Hash each piece of data and add to the tree
12+
for _, data in ipairs(data_table) do
13+
table.insert(tree, hash(data))
14+
end
15+
16+
-- Build the tree by hashing pairs of nodes until a single hash (the root) is obtained
17+
while #tree > 1 do
18+
local temp_tree = {}
19+
for i = 1, #tree, 2 do
20+
if i + 1 <= #tree then
21+
local concatenated_hashes = tree[i] .. tree[i + 1]
22+
table.insert(temp_tree, hash(concatenated_hashes))
23+
else
24+
table.insert(temp_tree, tree[i])
25+
end
26+
end
27+
tree = temp_tree
28+
end
29+
30+
return tree[1] -- The Merkle root
31+
end
32+
33+
-- Function to verify the integrity of a Merkle tree
34+
local function verify_merkle_tree(data_table, merkle_root)
35+
local computed_root = create_merkle_tree(data_table)
36+
return computed_root == merkle_root
37+
end
38+
39+
local data = {
40+
"data1",
41+
"data2",
42+
"data3",
43+
"data4"
44+
}
45+
46+
print'--test 1'
47+
local merkle_root = create_merkle_tree(data)
48+
assert(verify_merkle_tree(data, merkle_root))
49+
50+
data[2] = "tampered_data"
51+
assert(not verify_merkle_tree(data, merkle_root))
52+
53+
print'test 1--OK'
54+
55+
local data = {
56+
"data1"
57+
}
58+
59+
print'--test 2'
60+
local merkle_root = create_merkle_tree(data)
61+
assert(verify_merkle_tree(data, merkle_root))
62+
63+
data[1] = "tampered_data"
64+
assert(not verify_merkle_tree(data, merkle_root))
65+
66+
print'test 2--OK'
67+
68+
local data = {
69+
tostring(OCTET.random(32)),
70+
tostring(OCTET.random(32)),
71+
tostring(OCTET.random(32))
72+
}
73+
74+
print'--test 3'
75+
local merkle_root = create_merkle_tree(data)
76+
assert(verify_merkle_tree(data, merkle_root))
77+
78+
print 'test 3--OK'
79+
80+
print'--OK'

test/zencode/merkle_tree.bats

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
load ../bats_setup
2+
load ../bats_zencode
3+
SUBDOC=merkle_tree
4+
5+
@test "merkle root: array" {
6+
cat <<EOF | save_asset merkle_root_array.data.json
7+
{
8+
"data": [
9+
"data1",
10+
"data2",
11+
"data3",
12+
"data4"
13+
]
14+
}
15+
EOF
16+
cat <<EOF | zexe merkle_root_array.zen merkle_root_array.data.json
17+
Scenario 'merkle': create merkle root
18+
Given I have a 'string array' named 'data'
19+
20+
When I create the merkle root of 'data'
21+
22+
Then print the 'merkle root'
23+
EOF
24+
save_output merkle_root_array.out.json
25+
assert_output '{"merkle_root":"1Fu3eBfOGVlDihcanmfcZj45yuy4Z3/SrSq1iupzD/Q="}'
26+
}
27+
28+
@test "merkle root: from dictionary path" {
29+
cat <<EOF | save_asset merkle_root_path.data.json
30+
{
31+
"data": {
32+
"data1": [
33+
"data1",
34+
"data2",
35+
"data3",
36+
"data4"
37+
],
38+
"data2":"data2"
39+
}
40+
}
41+
EOF
42+
cat <<EOF | zexe merkle_root_path.zen merkle_root_path.data.json
43+
Scenario 'merkle': create merkle root
44+
Given I have a 'string dictionary' named 'data'
45+
46+
When I create the merkle root of dictionary path 'data.data1'
47+
48+
Then print the 'merkle root'
49+
EOF
50+
save_output merkle_root_path.out.json
51+
assert_output '{"merkle_root":"1Fu3eBfOGVlDihcanmfcZj45yuy4Z3/SrSq1iupzD/Q="}'
52+
}

0 commit comments

Comments
 (0)