From a3aa6dc3da024225d1de3212cb2b2919ce758ceb Mon Sep 17 00:00:00 2001 From: Lucas Oliveira Date: Sun, 24 Dec 2023 15:15:43 -0300 Subject: [PATCH 01/17] update: gitignore to support merkletreejs tests --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 8079cec..4581abf 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +node_modules .hypothesis .vscode *cache* From 921518ce252b9f02fd8ea2d3cdfd600357f15344 Mon Sep 17 00:00:00 2001 From: Lucas Oliveira Date: Sun, 24 Dec 2023 15:17:04 -0300 Subject: [PATCH 02/17] update: rename and update merkle root benchmark tests for support new compatibility with merkletreejs #21 --- ...kle_proof.py => test_merkle_proof_benchmark.py} | 2 +- ...eck.py => test_merkle_proof_check_benchmark.py} | 2 +- ...erkle_root.py => test_merkle_root_benchmark.py} | 14 +++++++------- 3 files changed, 9 insertions(+), 9 deletions(-) rename test/benchmark/{test_merkle_proof.py => test_merkle_proof_benchmark.py} (95%) rename test/benchmark/{test_merkle_proof_check.py => test_merkle_proof_check_benchmark.py} (96%) rename test/benchmark/{test_merkle_root.py => test_merkle_root_benchmark.py} (68%) diff --git a/test/benchmark/test_merkle_proof.py b/test/benchmark/test_merkle_proof_benchmark.py similarity index 95% rename from test/benchmark/test_merkle_proof.py rename to test/benchmark/test_merkle_proof_benchmark.py index 0614fe1..9ee3b24 100644 --- a/test/benchmark/test_merkle_proof.py +++ b/test/benchmark/test_merkle_proof_benchmark.py @@ -36,4 +36,4 @@ def test_create_proof_100_leaves(benchmark): @pytest.mark.benchmark(group="MerkleTreeProof", timer=time.time) def test_create_proof_1000_leaves(benchmark): - benchmark(create_proof_1000_leaves) + benchmark(create_proof_1000_leaves) \ No newline at end of file diff --git a/test/benchmark/test_merkle_proof_check.py b/test/benchmark/test_merkle_proof_check_benchmark.py similarity index 96% rename from test/benchmark/test_merkle_proof_check.py rename to test/benchmark/test_merkle_proof_check_benchmark.py index b4339ea..80bd67c 100644 --- a/test/benchmark/test_merkle_proof_check.py +++ b/test/benchmark/test_merkle_proof_check_benchmark.py @@ -36,4 +36,4 @@ def test_verify_proof_100_leaves(benchmark): @pytest.mark.benchmark(group="MerkleTreeVerify", timer=time.time) def test_verify_proof_1000_leaves(benchmark): - benchmark(verify_proof_1000_leaves) + benchmark(verify_proof_1000_leaves) \ No newline at end of file diff --git a/test/benchmark/test_merkle_root.py b/test/benchmark/test_merkle_root_benchmark.py similarity index 68% rename from test/benchmark/test_merkle_root.py rename to test/benchmark/test_merkle_root_benchmark.py index f58734f..d1c85e6 100644 --- a/test/benchmark/test_merkle_root.py +++ b/test/benchmark/test_merkle_root_benchmark.py @@ -6,22 +6,22 @@ def create_merkle_tree_root_10_leaves(): leafs = [str(i) for i in range(10)] tree = MerkleTree(leafs) - result = "f8b45f3031274577651c6d43e7ec3f7361e82b2295e24a9b369693354d3a2db8" - assert tree.root == result + result = "2a3eb5e4fd7ca38eebd660d4b9879fd3e235cd240772bccdfadfa6c1529b4711" + assert tree.root.hex() == result def create_merkle_tree_root_100_leaves(): leafs = [str(i) for i in range(100)] tree = MerkleTree(leafs) - result = "8e92f7efa075e532b920ef39adb04a0147ac84d99315584dbd2a1cb868019c35" - assert tree.root == result + result = "b46bf20bce2aafc0abe89f56509648c98fbad9f969d12869b40b4472845e2318" + assert tree.root.hex() == result def create_merkle_tree_root_1000_leaves(): leafs = [str(i) for i in range(1000)] tree = MerkleTree(leafs) - result = "e66024476f6ef8f431f07dca6ea0d10dd904dda4b47488a49e75f8671ba733ee" - assert tree.root == result + result = "fc0bdf832532d9d94510c8643720f63f22db9996f07965e1d1da9fb0d3fd7144" + assert tree.root.hex() == result @pytest.mark.benchmark(group="MerkleTreeRoot", timer=time.time) @@ -36,4 +36,4 @@ def test_create_merkle_tree_root_100_leaves(benchmark): @pytest.mark.benchmark(group="MerkleTreeRoot", timer=time.time) def test_create_merkle_tree_root_1000_leaves(benchmark): - benchmark(create_merkle_tree_root_1000_leaves) + benchmark(create_merkle_tree_root_1000_leaves) \ No newline at end of file From 0838a3af49a6f988610eb12030ea9e1a11d5cd81 Mon Sep 17 00:00:00 2001 From: Lucas Oliveira Date: Sun, 24 Dec 2023 15:18:00 -0300 Subject: [PATCH 03/17] add: erros file for tests to separated tests --- test/errors/test_errors.py | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 test/errors/test_errors.py diff --git a/test/errors/test_errors.py b/test/errors/test_errors.py new file mode 100644 index 0000000..b584e3b --- /dev/null +++ b/test/errors/test_errors.py @@ -0,0 +1,27 @@ +from merkly.utils import InvalidHashFunctionError +from merkly.mtree import MerkleTree +from pytest import raises + + +def test_make_proof_value_error(): + leafs = ["a", "b", "c", "d", "e", "f", "g", "h"] + tree = MerkleTree(leafs) + + invalid_leaf = "invalid" + with raises(ValueError) as error: + tree.make_proof(leafs, [], invalid_leaf) + + assert ( + str(error.value) == f"Leaf: {invalid_leaf} does not exist in the tree: {leafs}" + ) + + +def test_invalid_hash_function_error(): + def invalid_hash_function(data): + return 123 + + with raises(InvalidHashFunctionError): + MerkleTree( + ["a", "b", "c", "d"], + invalid_hash_function, + ) From 33374ba78541bd61d3540e4d65b9c20473dfc539 Mon Sep 17 00:00:00 2001 From: Lucas Oliveira Date: Sun, 24 Dec 2023 15:18:32 -0300 Subject: [PATCH 04/17] add: merkle proof file for move tests by context --- test/merkle_proof/test_merkle_proof.py | 49 ++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 test/merkle_proof/test_merkle_proof.py diff --git a/test/merkle_proof/test_merkle_proof.py b/test/merkle_proof/test_merkle_proof.py new file mode 100644 index 0000000..b2e6a7a --- /dev/null +++ b/test/merkle_proof/test_merkle_proof.py @@ -0,0 +1,49 @@ +from pytest import mark +from merkly.mtree import MerkleTree +from merkly.node import Node, Side + + +def test_proof_simple_odd_merkle(): + leafs = ["a", "b", "c", "d", "e"] + tree = MerkleTree(leafs, lambda x, y: x + y) + proof = [ + Node(data=b"abcd", side=Side.LEFT), + ] + + result = tree.proof("e") + assert result == proof + + +def test_proof_simple_merkle(): + leafs = ["a", "b", "c", "d"] + tree = MerkleTree(leafs) + proof = [ + Node( + side=Side.RIGHT, + data=bytes.fromhex( + "b5553de315e0edf504d9150af82dafa5c4667fa618ed0a6f19c69b41166c5510" + ), + ), + Node( + side=Side.RIGHT, + data=bytes.fromhex( + "d253a52d4cb00de2895e85f2529e2976e6aaaa5c18106b68ab66813e14415669" + ), + ), + ] + + result = tree.proof("a") + assert result == proof + + +@mark.parametrize( + "leaf", + ["a", "b", "c", "d", "e", "f", "g", "h", "1", "2", "3", "4", "5", "6", "7", "8"], +) +def test_verify_merkle(leaf: str): + tree = MerkleTree( + ["a", "b", "c", "d", "e", "f", "g", "h", "1", "2", "3", "4", "5", "6", "7", "8"] + ) + + result = tree.proof(leaf) + assert tree.verify(result, leaf) From 9c899d313a64a204eacc69a86bf77cc258f818da Mon Sep 17 00:00:00 2001 From: Lucas Oliveira Date: Sun, 24 Dec 2023 15:18:42 -0300 Subject: [PATCH 05/17] add: merkle root file for move tests by context --- test/merkle_root/test_merkle_root.py | 174 +++++++++++++++++++++++++++ 1 file changed, 174 insertions(+) create mode 100644 test/merkle_root/test_merkle_root.py diff --git a/test/merkle_root/test_merkle_root.py b/test/merkle_root/test_merkle_root.py new file mode 100644 index 0000000..f9c1483 --- /dev/null +++ b/test/merkle_root/test_merkle_root.py @@ -0,0 +1,174 @@ +from merkly.mtree import MerkleTree +from typing import List +from pytest import mark +import hashlib + + +def test_simple_merkle_tree_constructor(): + leafs = ["a", "b", "c", "d"] + tree = MerkleTree(leafs) + + assert tree.raw_leafs == leafs + for i, j in zip( + tree.short_leafs, + [ + bytes.fromhex("3ac2"), + bytes.fromhex("b555"), + bytes.fromhex("0b42"), + bytes.fromhex("f191"), + ], + ): + assert i == j + assert tree.leafs == [ + bytes.fromhex("3ac225168df54212a25c1c01fd35bebfea408fdac2e31ddd6f80a4bbf9a5f1cb"), + bytes.fromhex("b5553de315e0edf504d9150af82dafa5c4667fa618ed0a6f19c69b41166c5510"), + bytes.fromhex("0b42b6393c1f53060fe3ddbfcd7aadcca894465a5a438f69c87d790b2299b9b2"), + bytes.fromhex("f1918e8562236eb17adc8502332f4c9c82bc14e19bfc0aa10ab674ff75b3d2f3"), + ] + + assert ( + tree.root.hex() + == "68203f90e9d07dc5859259d7536e87a6ba9d345f2552b5b9de2999ddce9ce1bf" + ) + + +@mark.parametrize( + "leafs, root", + [ + ( + ["a", "b", "c", "d", "e", "f", "g", "h", "1"], + "85d75312120b9d7cc325fef61d5c3b5de921a77f11049b187ddc5b90a3172c6d", + ), + ( + ["a", "b", "c", "d", "e", "f", "g", "h"], + "cd07272f4955ddcfdac38ff36dff9d3e4353498923679ab548ba87e34648e4a3", + ), + ( + ["a", "b", "c", "d", "e"], + "1dd0d2a6ae466d665cb26e1a31f07c57ae5df7d2bc559cd5826d417be9141a5d", + ), + ( + ["a", "b", "c", "d"], + "68203f90e9d07dc5859259d7536e87a6ba9d345f2552b5b9de2999ddce9ce1bf", + ), + ( + ["a", "b"], + "805b21d846b189efaeb0377d6bb0d201b3872a363e607c25088f025b0c6ae1f8", + ), + ], +) +def test_simple_merkle_root_with_keccak256(leafs: List[str], root: str): + tree = MerkleTree(leafs) + result = tree.root.hex() + assert result == root + + +@mark.parametrize( + "leafs, root", + [ + ( + ["a", "b", "c", "d", "e", "f", "g", "h", "1"], + "44611cac12a28960fa9f8d8bf93d9d73944b1425f3ba41d9cffe45c3aa3403d6", + ), + ( + ["a", "b", "c", "d", "e", "f", "g", "h"], + "bd7c8a900be9b67ba7df5c78a652a8474aedd78adb5083e80e49d9479138a23f", + ), + ( + ["a", "b", "c", "d", "e"], + "d71f8983ad4ee170f8129f1ebcdd7440be7798d8e1c80420bf11f1eced610dba", + ), + ( + ["a", "b", "c", "d"], + "14ede5e8e97ad9372327728f5099b95604a39593cac3bd38a343ad76205213e7", + ), + ( + ["a", "b"], + "e5a01fee14e0ed5c48714f22180f25ad8365b53f9779f79dc4a3d7e93963f94a", + ), + ], +) +def test_simple_merkle_root_with_sha_256(leafs: List[str], root: str): + def sha_256(x: bytes, y: bytes) -> bytes: + data = x + y + h = hashlib.sha256() + h.update(data) + return h.digest() + + tree = MerkleTree(leafs, hash_function=sha_256) + result = tree.root.hex() + assert result == root + + +@mark.parametrize( + "leafs, root", + [ + ( + ["a", "b", "c", "d", "e", "f", "g", "h", "1"], + "bf4835202b9091df8d6e44c0e36094fc5dee200ef2aeb385299e0e73d289947a", + ), + ( + ["a", "b", "c", "d", "e", "f", "g", "h"], + "c82d4fe15c85db42ec73121b5c482d86b95b78e05db8f707cde754e2bde30195", + ), + ( + ["a", "b", "c", "d", "e"], + "c79cb7cae8eeca849c11c804ccfde50216bbe143cd557cc0a8bb877c66496e4e", + ), + ( + ["a", "b", "c", "d"], + "565a2fdea5772b9dffdbb0081beeee36b2e9b952a8ef34fc5a58653fe4f9bd3d", + ), + ( + ["a", "b"], + "fb53027dcbe9bb65748239cf200d4512367aafe81c683d0584491bfe7b644279", + ), + ], +) +def test_simple_merkle_root_with_shake256(leafs: List[str], root: str): + def shake_256(x: bytes, y: bytes) -> bytes: + data = x + y + h = hashlib.shake_256() + h.update(data) + return h.digest(32) + + tree = MerkleTree(leafs, hash_function=shake_256) + result = tree.root.hex() + assert result == root + + +@mark.parametrize( + "leafs, root", + [ + ( + ["a", "b", "c", "d", "e", "f", "g", "h", "1"], + "8e106437be21dfca61170ed413a185a73cd081a4a5760a95df4ccc5488874260", + ), + ( + ["a", "b", "c", "d", "e", "f", "g", "h"], + "463baccb1666a42a1156e67f5961be418728a5bd80a97fda6d5054496d7646dc", + ), + ( + ["a", "b", "c", "d", "e"], + "b8efa384f64647583db7ea069c46ec746d4d8c0c1815040431db4134bc0b41fd", + ), + ( + ["a", "b", "c", "d"], + "5267fec4a5327f9d287233f95213afa39d3aad2fee1fa1384b032b79fb3441e8", + ), + ( + ["a", "b"], + "29df505440ebe180c00857e92b0694c56a33762b08944472492b0cbf6ec607e3", + ), + ], +) +def test_simple_merkle_root_with_sha3_256(leafs: List[str], root: str): + def sha3_256(x: bytes, y: bytes) -> bytes: + data = x + y + h = hashlib.sha3_256() + h.update(data) + return h.digest() + + tree = MerkleTree(leafs, hash_function=sha3_256) + result = tree.root.hex() + assert result == root From 53d0eeee49d439582116986d2d142d7703b5cdf1 Mon Sep 17 00:00:00 2001 From: Lucas Oliveira Date: Sun, 24 Dec 2023 15:18:59 -0300 Subject: [PATCH 06/17] remove: unused code --- test/test_merkle_tree.py | 154 --------------------------------------- 1 file changed, 154 deletions(-) delete mode 100644 test/test_merkle_tree.py diff --git a/test/test_merkle_tree.py b/test/test_merkle_tree.py deleted file mode 100644 index 8a1f613..0000000 --- a/test/test_merkle_tree.py +++ /dev/null @@ -1,154 +0,0 @@ -""" -Testing Merkle Tree -""" - -from merkly.node import Side -from merkly.utils import InvalidHashFunctionError -from merkly.mtree import MerkleTree, Node -from pytest import raises, mark -from typing import List - - -def test_simple_merkle_tree_constructor(): - """ - Instantiated a simple Merkle Tree - """ - leafs = ["a", "b", "c", "d"] - tree = MerkleTree(leafs) - - assert tree.raw_leafs == leafs - assert tree.short_leafs == [ - "3ac2...", - "b555...", - "0b42...", - "f191...", - ] - assert tree.leafs == [ - "3ac225168df54212a25c1c01fd35bebfea408fdac2e31ddd6f80a4bbf9a5f1cb", - "b5553de315e0edf504d9150af82dafa5c4667fa618ed0a6f19c69b41166c5510", - "0b42b6393c1f53060fe3ddbfcd7aadcca894465a5a438f69c87d790b2299b9b2", - "f1918e8562236eb17adc8502332f4c9c82bc14e19bfc0aa10ab674ff75b3d2f3", - ] - - -@mark.parametrize( - "leafs, root", - [ - ( - ["a", "b", "c", "d", "e", "f", "g", "h", "1"], - "edcaf6c7e68ed0f1e6f9e6cef0b5be147172cdb2d1c10d0285123bb425c2ad4c", - ), - ( - ["a", "b", "c", "d", "e", "f", "g", "h"], - "2dfe93948ecb1a0903dbf034de56d6529e62679519a37ed3b5b3356ab27b7bb8", - ), - ( - ["a", "b", "c", "d", "e"], - "eaccbf1a24f8bfe6b2b4c3be14a4a782080fab07e3ecc81effa7e4d26f8daf80", - ), - ( - ["a", "b", "c", "d"], - "115cbb4775ed495f3d954dfa47164359a97762b40059d9502895def16eed609c", - ), - ( - ["a", "b"], - "414e3a845393ef6d68973ddbf5bd85ff524443cf0e06a361624f3d51b879ec1c", - ), - (["a"], "3ac225168df54212a25c1c01fd35bebfea408fdac2e31ddd6f80a4bbf9a5f1cb"), - ], -) -def test_simple_merkle_root(leafs: List[str], root: str): - """ - Generate a Root of a simple Merkle Tree - """ - tree = MerkleTree(leafs) - assert tree.root == root - - -def test_proof_simple_odd_merkle(): - """ - Instantiated a simple Merkle Tree - """ - leafs = ["a", "b", "c", "d", "e"] - tree = MerkleTree(leafs, lambda x, y: x + y) - proof = [ - Node(data="abcd", side=Side.LEFT), - ] - - assert tree.proof("e") == proof, "Proofs dont's match" - assert tree.verify(proof, "e"), "Proof dont's right" - - -def test_proof_simple_merkle(): - """ - Instantiated a simple Merkle Tree - """ - leafs = ["a", "b", "c", "d"] - tree = MerkleTree(leafs) - proof = [ - Node( - side=Side.RIGHT, - data="b5553de315e0edf504d9150af82dafa5c4667fa618ed0a6f19c69b41166c5510", - ), - Node( - side=Side.RIGHT, - data="64673cf40035df6d3a0d0143cc8426de49b9a93b9ad2d330cb4f0bc390a86d20", - ), - ] - - assert tree.proof("a") == proof - assert tree.verify(proof, "a") - - -@mark.parametrize( - "leaf", - ["a", "b", "c", "d", "e", "f", "g", "h", "1", "2", "3", "4", "5", "6", "7", "8"], -) -def test_verify_simple_merkle(leaf: str): - """ - Instantiated a simple Merkle Tree - """ - tree = MerkleTree( - ["a", "b", "c", "d", "e", "f", "g", "h", "1", "2", "3", "4", "5", "6", "7", "8"] - ) - - assert tree.verify(tree.proof(leaf), leaf), "Proof is False" - - -def test_make_proof_value_error(): - """ - Testa a captura de ValueError na função make_proof - """ - leafs = ["a", "b", "c", "d", "e", "f", "g", "h"] - tree = MerkleTree(leafs) - - invalid_leaf = "invalid" - with raises(ValueError) as error: - tree.make_proof(leafs, [], invalid_leaf) - - assert ( - str(error.value) == f"Leaf: {invalid_leaf} does not exist in the tree: {leafs}" - ) - - -def test_merkle_tree_repr(): - """ - Testa a representação em string (__repr__) da classe MerkleTree - """ - leafs = ["a", "b", "c", "d", "e", "f", "g", "h"] - tree = MerkleTree(leafs, lambda x, y: f"{x}{y}1") - - expected_repr = """MerkleTree(\nraw_leafs: ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h']\nleafs: ['a1', 'b1', 'c1', 'd1', 'e1', 'f1', 'g1', 'h1']\nshort_leafs: ['a1...', 'b1...', 'c1...', 'd1...', 'e1...', 'f1...', 'g1...', 'h1...'])""" - - assert repr(tree) == expected_repr - - -def test_invalid_hash_function_error(): - def invalid_hash_function_that_returns_an_integer_instead_of_a_string(data): - return 123 - - with raises(InvalidHashFunctionError): - MerkleTree( - ["a", "b", "c", "d"], - invalid_hash_function_that_returns_an_integer_instead_of_a_string, - ) From 8c61d3bacd954c01f896df4f9e6f9ab367a73d93 Mon Sep 17 00:00:00 2001 From: Lucas Oliveira Date: Sun, 24 Dec 2023 15:19:31 -0300 Subject: [PATCH 07/17] add: tests for compatibility in merkle root --- test/merkletreejs/merkle_root/merkle_root_test.js | 8 ++++++++ test/merkletreejs/merkle_root/merkle_root_test.py | 13 +++++++++++++ 2 files changed, 21 insertions(+) create mode 100644 test/merkletreejs/merkle_root/merkle_root_test.js create mode 100644 test/merkletreejs/merkle_root/merkle_root_test.py diff --git a/test/merkletreejs/merkle_root/merkle_root_test.js b/test/merkletreejs/merkle_root/merkle_root_test.js new file mode 100644 index 0000000..5f3e2ad --- /dev/null +++ b/test/merkletreejs/merkle_root/merkle_root_test.js @@ -0,0 +1,8 @@ +const { MerkleTree } = require('merkletreejs'); +const SHA256 = require('crypto-js/sha256'); + +const leaves = ['a', 'b', 'c', 'd'].map(SHA256); +const tree = new MerkleTree(leaves, SHA256, {}); +const root = tree.getRoot().toString('hex'); + +console.log(JSON.stringify({ root })); diff --git a/test/merkletreejs/merkle_root/merkle_root_test.py b/test/merkletreejs/merkle_root/merkle_root_test.py new file mode 100644 index 0000000..2312cfd --- /dev/null +++ b/test/merkletreejs/merkle_root/merkle_root_test.py @@ -0,0 +1,13 @@ +from merkly.mtree import MerkleTree +import hashlib +import json + +def sha256(x, y): + data = x + y + return hashlib.sha256(data).digest() + +leaves = ["a", "b", "c", "d"] +tree = MerkleTree(leaves, sha256) +root = tree.root.hex() + +print(json.dumps({"root": root})) From a085e583ef5ab094e8467643f245de5354567075 Mon Sep 17 00:00:00 2001 From: Lucas Oliveira Date: Sun, 24 Dec 2023 15:19:44 -0300 Subject: [PATCH 08/17] add: tests for compatibility in merkle proof --- .../merkle_proof/merkle_proof_test.js | 9 +++++++++ .../merkle_proof/merkle_proof_test.py | 15 +++++++++++++++ 2 files changed, 24 insertions(+) create mode 100644 test/merkletreejs/merkle_proof/merkle_proof_test.js create mode 100644 test/merkletreejs/merkle_proof/merkle_proof_test.py diff --git a/test/merkletreejs/merkle_proof/merkle_proof_test.js b/test/merkletreejs/merkle_proof/merkle_proof_test.js new file mode 100644 index 0000000..4d2e44c --- /dev/null +++ b/test/merkletreejs/merkle_proof/merkle_proof_test.js @@ -0,0 +1,9 @@ +const { MerkleTree } = require('merkletreejs'); +const SHA256 = require('crypto-js/sha256'); + +const leaves = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'].map(x => SHA256(x)); +const tree = new MerkleTree(leaves, SHA256); +const leaf = SHA256('a'); +const proof = tree.getProof(leaf).map(node => ({ data: node.data.toString('hex'), position: node.position })); + +console.log(JSON.stringify({ proof, isValid: tree.verify(proof, leaf, tree.getRoot()) })) diff --git a/test/merkletreejs/merkle_proof/merkle_proof_test.py b/test/merkletreejs/merkle_proof/merkle_proof_test.py new file mode 100644 index 0000000..70e6c53 --- /dev/null +++ b/test/merkletreejs/merkle_proof/merkle_proof_test.py @@ -0,0 +1,15 @@ +from merkly.mtree import MerkleTree +import hashlib +import json + +def sha256(x, y): + data = x + y + return hashlib.sha256(data).digest() + +leaves = ["a", "b", "c", "d", "e", "f", "g", "h"] +tree = MerkleTree(leaves, sha256) +leaf = 'a' +proof = tree.proof(leaf) +formatted_proof = [{"data": node.data.hex(), "position": node.side.name.lower()} for node in proof] + +print(json.dumps({"proof": formatted_proof, "isValid": tree.verify(proof, leaf)})) \ No newline at end of file From 2914e1b2983a67cf40050eae5b8cc1dec9b67de0 Mon Sep 17 00:00:00 2001 From: Lucas Oliveira Date: Sun, 24 Dec 2023 15:20:04 -0300 Subject: [PATCH 09/17] add: tests for compatibility with merkletreejs --- test/merkletreejs/package.json | 10 ++++++ .../test_merkle_proof_compatibility.py | 30 +++++++++++++++++ .../test_merkle_root_compatibility.py | 32 +++++++++++++++++++ 3 files changed, 72 insertions(+) create mode 100644 test/merkletreejs/package.json create mode 100644 test/merkletreejs/test_merkle_proof_compatibility.py create mode 100644 test/merkletreejs/test_merkle_root_compatibility.py diff --git a/test/merkletreejs/package.json b/test/merkletreejs/package.json new file mode 100644 index 0000000..120e486 --- /dev/null +++ b/test/merkletreejs/package.json @@ -0,0 +1,10 @@ +{ + "name": "merkletreejs", + "version": "1.0.0", + "main": "index.js", + "license": "MIT", + "dependencies": { + "merkletreejs": "^0.3.11", + "web3": "^4.3.0" + } +} \ No newline at end of file diff --git a/test/merkletreejs/test_merkle_proof_compatibility.py b/test/merkletreejs/test_merkle_proof_compatibility.py new file mode 100644 index 0000000..ad756dc --- /dev/null +++ b/test/merkletreejs/test_merkle_proof_compatibility.py @@ -0,0 +1,30 @@ +from pytest import mark +import subprocess +import json + + +@mark.merkletreejs +def test_merkle_proof_compatibility_between_merkletreejs_and_merkly(): + result = subprocess.run(["yarn"], check=False) + + assert result.returncode == 0, result.stderr + + result_js = subprocess.run( + ["node", "./test/merkletreejs/merkle_proof/merkle_proof_test.js"], + capture_output=True, + text=True, + check=True, + ) + assert result_js.returncode == 0, result_js.stderr + data_js = json.loads(result_js.stdout) + + result_py = subprocess.run( + ["python", "./test/merkletreejs/merkle_proof/merkle_proof_test.py"], + capture_output=True, + text=True, + check=True, + ) + assert result_py.returncode == 0, result_py.stderr + data_py = json.loads(result_py.stdout) + + assert data_js == data_py diff --git a/test/merkletreejs/test_merkle_root_compatibility.py b/test/merkletreejs/test_merkle_root_compatibility.py new file mode 100644 index 0000000..002e7b8 --- /dev/null +++ b/test/merkletreejs/test_merkle_root_compatibility.py @@ -0,0 +1,32 @@ +from pytest import mark +import subprocess +import json + + + +@mark.merkletreejs +def test_merkle_root_compatibility_between_merkletreejs_and_merkly(): + result = subprocess.run(["yarn"], check=False) + + assert result.returncode == 0, result.stderr + + result_js = subprocess.run( + ["node", "./test/merkletreejs/merkle_root/merkle_root_test.js"], + capture_output=True, + text=True, + check=False, + ) + assert result_js.returncode == 0, result_js.stderr + merkle_root_js = json.loads(result_js.stdout) + + result_py = subprocess.run( + ["python", "./test/merkletreejs/merkle_root/merkle_root_test.py"], + capture_output=True, + text=True, + check=False, + ) + assert result_py.returncode == 0, result_py.stderr + merkle_root_py = json.loads(result_py.stdout) + + assert merkle_root_js == merkle_root_py + From a904d0e2f693dd14107842c7bac8ed9eb2f56bdb Mon Sep 17 00:00:00 2001 From: Lucas Oliveira Date: Sun, 24 Dec 2023 15:25:04 -0300 Subject: [PATCH 10/17] update: str to bytes and add validation in leafs --- merkly/utils.py | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/merkly/utils.py b/merkly/utils.py index 7beee1d..d7eb232 100644 --- a/merkly/utils.py +++ b/merkly/utils.py @@ -19,11 +19,11 @@ class InvalidHashFunctionError(Exception): """Exception raised for invalid hash function.""" def __init__(self) -> None: - self.message = "Must type of: (str) -> str" + self.message = "Must type of: (bytes, bytes) -> bytes" super().__init__(self.message) -def keccak(data: str) -> str: +def keccak(data: bytes) -> bytes: """ # Hash `data: str` using keccak256 - params `data: str` @@ -45,9 +45,9 @@ def keccak(data: str) -> str: """ keccak_256 = cryptodome_keccak.new(digest_bits=256) - keccak_256.update(data.encode()) + keccak_256.update(data) - return keccak_256.hexdigest() + return keccak_256.digest() def half(list_item: List[int]) -> Tuple[int, int]: @@ -88,11 +88,23 @@ def slice_in_pairs(list_item: list): return [list_item[i : i + 2] for i in range(0, len(list_item), 2)] -def hash_function_type_checking(hash_function: Callable[[str], str]) -> bool: +def validate_leafs(leafs: List[str]): + size = len(leafs) + + if size < 2: + raise Exception("Invalid size, need > 2") + + a = isinstance(leafs, List) + b = all(isinstance(leaf, str) for leaf in leafs) + if not (a and b): + raise Exception("Invalid type of leafs") + + +def validate_hash_function(hash_function: Callable[[bytes, bytes], bytes]): a = isinstance(hash_function, types.FunctionType) b = callable(hash_function) try: - c = isinstance(hash_function(str(), str()), str) + c = isinstance(hash_function(bytes(), bytes()), bytes) except TypeError: c = False From c0505009e3117d558c4e3e5ff031aeee5848b8cc Mon Sep 17 00:00:00 2001 From: Lucas Oliveira Date: Sun, 24 Dec 2023 15:25:36 -0300 Subject: [PATCH 11/17] add: and in node class --- merkly/node.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/merkly/node.py b/merkly/node.py index 838197c..856f2b9 100644 --- a/merkly/node.py +++ b/merkly/node.py @@ -13,5 +13,11 @@ class Node(BaseModel): # 🍃 Leaf of Merkle Tree """ - data: Optional[str] = None + data: Optional[bytes] = None side: Side = Side.LEFT + + def __eq__(self, other: "Node") -> bool: + return self.data == other.data + + def __repr__(self) -> str: + return f"Node({self.data.hex()}, {self.side})" \ No newline at end of file From 8258ab6a8f233ca655af184ea2c404330d3b5bcc Mon Sep 17 00:00:00 2001 From: Lucas Oliveira Date: Sun, 24 Dec 2023 15:33:25 -0300 Subject: [PATCH 12/17] update: just format with black --- merkly/node.py | 2 +- test/benchmark/test_merkle_proof_benchmark.py | 2 +- .../test_merkle_proof_check_benchmark.py | 2 +- test/benchmark/test_merkle_root_benchmark.py | 2 +- test/merkle_root/test_merkle_root.py | 16 ++++++++++++---- .../merkle_proof/merkle_proof_test.py | 10 +++++++--- .../merkletreejs/merkle_root/merkle_root_test.py | 2 ++ .../test_merkle_root_compatibility.py | 2 -- 8 files changed, 25 insertions(+), 13 deletions(-) diff --git a/merkly/node.py b/merkly/node.py index 856f2b9..0931a19 100644 --- a/merkly/node.py +++ b/merkly/node.py @@ -20,4 +20,4 @@ def __eq__(self, other: "Node") -> bool: return self.data == other.data def __repr__(self) -> str: - return f"Node({self.data.hex()}, {self.side})" \ No newline at end of file + return f"Node({self.data.hex()}, {self.side})" diff --git a/test/benchmark/test_merkle_proof_benchmark.py b/test/benchmark/test_merkle_proof_benchmark.py index 9ee3b24..0614fe1 100644 --- a/test/benchmark/test_merkle_proof_benchmark.py +++ b/test/benchmark/test_merkle_proof_benchmark.py @@ -36,4 +36,4 @@ def test_create_proof_100_leaves(benchmark): @pytest.mark.benchmark(group="MerkleTreeProof", timer=time.time) def test_create_proof_1000_leaves(benchmark): - benchmark(create_proof_1000_leaves) \ No newline at end of file + benchmark(create_proof_1000_leaves) diff --git a/test/benchmark/test_merkle_proof_check_benchmark.py b/test/benchmark/test_merkle_proof_check_benchmark.py index 80bd67c..b4339ea 100644 --- a/test/benchmark/test_merkle_proof_check_benchmark.py +++ b/test/benchmark/test_merkle_proof_check_benchmark.py @@ -36,4 +36,4 @@ def test_verify_proof_100_leaves(benchmark): @pytest.mark.benchmark(group="MerkleTreeVerify", timer=time.time) def test_verify_proof_1000_leaves(benchmark): - benchmark(verify_proof_1000_leaves) \ No newline at end of file + benchmark(verify_proof_1000_leaves) diff --git a/test/benchmark/test_merkle_root_benchmark.py b/test/benchmark/test_merkle_root_benchmark.py index d1c85e6..894aa5f 100644 --- a/test/benchmark/test_merkle_root_benchmark.py +++ b/test/benchmark/test_merkle_root_benchmark.py @@ -36,4 +36,4 @@ def test_create_merkle_tree_root_100_leaves(benchmark): @pytest.mark.benchmark(group="MerkleTreeRoot", timer=time.time) def test_create_merkle_tree_root_1000_leaves(benchmark): - benchmark(create_merkle_tree_root_1000_leaves) \ No newline at end of file + benchmark(create_merkle_tree_root_1000_leaves) diff --git a/test/merkle_root/test_merkle_root.py b/test/merkle_root/test_merkle_root.py index f9c1483..0cbad50 100644 --- a/test/merkle_root/test_merkle_root.py +++ b/test/merkle_root/test_merkle_root.py @@ -20,10 +20,18 @@ def test_simple_merkle_tree_constructor(): ): assert i == j assert tree.leafs == [ - bytes.fromhex("3ac225168df54212a25c1c01fd35bebfea408fdac2e31ddd6f80a4bbf9a5f1cb"), - bytes.fromhex("b5553de315e0edf504d9150af82dafa5c4667fa618ed0a6f19c69b41166c5510"), - bytes.fromhex("0b42b6393c1f53060fe3ddbfcd7aadcca894465a5a438f69c87d790b2299b9b2"), - bytes.fromhex("f1918e8562236eb17adc8502332f4c9c82bc14e19bfc0aa10ab674ff75b3d2f3"), + bytes.fromhex( + "3ac225168df54212a25c1c01fd35bebfea408fdac2e31ddd6f80a4bbf9a5f1cb" + ), + bytes.fromhex( + "b5553de315e0edf504d9150af82dafa5c4667fa618ed0a6f19c69b41166c5510" + ), + bytes.fromhex( + "0b42b6393c1f53060fe3ddbfcd7aadcca894465a5a438f69c87d790b2299b9b2" + ), + bytes.fromhex( + "f1918e8562236eb17adc8502332f4c9c82bc14e19bfc0aa10ab674ff75b3d2f3" + ), ] assert ( diff --git a/test/merkletreejs/merkle_proof/merkle_proof_test.py b/test/merkletreejs/merkle_proof/merkle_proof_test.py index 70e6c53..eaa3957 100644 --- a/test/merkletreejs/merkle_proof/merkle_proof_test.py +++ b/test/merkletreejs/merkle_proof/merkle_proof_test.py @@ -2,14 +2,18 @@ import hashlib import json + def sha256(x, y): data = x + y return hashlib.sha256(data).digest() + leaves = ["a", "b", "c", "d", "e", "f", "g", "h"] tree = MerkleTree(leaves, sha256) -leaf = 'a' +leaf = "a" proof = tree.proof(leaf) -formatted_proof = [{"data": node.data.hex(), "position": node.side.name.lower()} for node in proof] +formatted_proof = [ + {"data": node.data.hex(), "position": node.side.name.lower()} for node in proof +] -print(json.dumps({"proof": formatted_proof, "isValid": tree.verify(proof, leaf)})) \ No newline at end of file +print(json.dumps({"proof": formatted_proof, "isValid": tree.verify(proof, leaf)})) diff --git a/test/merkletreejs/merkle_root/merkle_root_test.py b/test/merkletreejs/merkle_root/merkle_root_test.py index 2312cfd..cb69112 100644 --- a/test/merkletreejs/merkle_root/merkle_root_test.py +++ b/test/merkletreejs/merkle_root/merkle_root_test.py @@ -2,10 +2,12 @@ import hashlib import json + def sha256(x, y): data = x + y return hashlib.sha256(data).digest() + leaves = ["a", "b", "c", "d"] tree = MerkleTree(leaves, sha256) root = tree.root.hex() diff --git a/test/merkletreejs/test_merkle_root_compatibility.py b/test/merkletreejs/test_merkle_root_compatibility.py index 002e7b8..44a69a6 100644 --- a/test/merkletreejs/test_merkle_root_compatibility.py +++ b/test/merkletreejs/test_merkle_root_compatibility.py @@ -3,7 +3,6 @@ import json - @mark.merkletreejs def test_merkle_root_compatibility_between_merkletreejs_and_merkly(): result = subprocess.run(["yarn"], check=False) @@ -29,4 +28,3 @@ def test_merkle_root_compatibility_between_merkletreejs_and_merkly(): merkle_root_py = json.loads(result_py.stdout) assert merkle_root_js == merkle_root_py - From 313a3b7920e60f8269287db7cd21d9a1828324fa Mon Sep 17 00:00:00 2001 From: Lucas Oliveira Date: Sun, 24 Dec 2023 15:34:28 -0300 Subject: [PATCH 13/17] update: str to bytes --- merkly/mtree.py | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/merkly/mtree.py b/merkly/mtree.py index 8ef3220..4e280b9 100644 --- a/merkly/mtree.py +++ b/merkly/mtree.py @@ -29,16 +29,15 @@ class MerkleTree: def __init__( self, leafs: List[str], - hash_function: Callable[[str], str] = lambda x, y: keccak(x + y), + hash_function: Callable[[bytes, bytes], bytes] = lambda x, y: keccak(x + y), ) -> None: - hash_function_type_checking(hash_function) - self.hash_function: Callable[[str], str] = hash_function + self.hash_function: Callable[[bytes, bytes], bytes] = hash_function self.raw_leafs: List[str] = leafs self.leafs: List[str] = self.__hash_leafs(leafs) self.short_leafs: List[str] = self.short(self.leafs) def __hash_leafs(self, leafs: List[str]) -> List[str]: - return list(map(lambda x: self.hash_function(x, ""), leafs)) + return list(map(lambda x: self.hash_function(x.encode(), bytes()), leafs)) def __repr__(self) -> str: return f"""MerkleTree(\nraw_leafs: {self.raw_leafs}\nleafs: {self.leafs}\nshort_leafs: {self.short(self.leafs)})""" @@ -47,14 +46,16 @@ def short(self, data: List[str]) -> List[str]: return [f"{x[:4]}..." for x in data] @property - def root(self) -> str: - return self.make_root(self.leafs)[0] + def root(self) -> bytes: + return self.make_root(self.leafs) def proof(self, raw_leaf: str) -> List[Node]: - return self.make_proof(self.leafs, [], self.hash_function(raw_leaf, "")) + return self.make_proof( + self.leafs, [], self.hash_function(raw_leaf.encode(), bytes()) + ) - def verify(self, proof: List[str], raw_leaf: str) -> bool: - full_proof = [self.hash_function(raw_leaf, "")] + def verify(self, proof: List[bytes], raw_leaf: str) -> bool: + full_proof = [self.hash_function(raw_leaf.encode(), bytes())] full_proof.extend(proof) def concat_nodes(left: Node, right: Node) -> Node: @@ -87,7 +88,9 @@ def make_root(self, leafs: List[str]) -> List[str]: ] ) - def make_proof(self, leafs: List[str], proof: List[Node], leaf: str) -> List[Node]: + def make_proof( + self, leafs: List[bytes], proof: List[Node], leaf: bytes + ) -> List[Node]: """ # Make a proof @@ -131,7 +134,7 @@ def make_proof(self, leafs: List[str], proof: List[Node], leaf: str) -> List[Nod return self.make_proof(right, proof, leaf) def mix_tree( - self, leaves: List[str], proof: List[Node], leaf_index: int + self, leaves: List[bytes], proof: List[Node], leaf_index: int ) -> List[Node]: if len(leaves) == 1: return proof @@ -146,7 +149,7 @@ def mix_tree( return self.mix_tree(self.up_layer(leaves), proof, leaf_index // 2) - def up_layer(self, leaves: List[str]) -> List[str]: + def up_layer(self, leaves: List[bytes]) -> List[bytes]: new_layer = [] for pair in slice_in_pairs(leaves): if len(pair) == 1: From 3f6fe0c70e0d99d77373e45d1d7d2c665c7f9741 Mon Sep 17 00:00:00 2001 From: Lucas Oliveira Date: Sun, 24 Dec 2023 15:35:06 -0300 Subject: [PATCH 14/17] add: validation for leafs and rename validation of hash function --- merkly/mtree.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/merkly/mtree.py b/merkly/mtree.py index 4e280b9..1530f67 100644 --- a/merkly/mtree.py +++ b/merkly/mtree.py @@ -7,11 +7,12 @@ from merkly.node import Node, Side from merkly.utils import ( - hash_function_type_checking, + validate_hash_function, is_power_2, slice_in_pairs, keccak, half, + validate_leafs, ) @@ -31,6 +32,8 @@ def __init__( leafs: List[str], hash_function: Callable[[bytes, bytes], bytes] = lambda x, y: keccak(x + y), ) -> None: + validate_leafs(leafs) + validate_hash_function(hash_function) self.hash_function: Callable[[bytes, bytes], bytes] = hash_function self.raw_leafs: List[str] = leafs self.leafs: List[str] = self.__hash_leafs(leafs) From 7ff841cf315a27a9a11faaf2948f243e6f02fc7b Mon Sep 17 00:00:00 2001 From: Lucas Oliveira Date: Sun, 24 Dec 2023 15:35:55 -0300 Subject: [PATCH 15/17] update: way of show short leafs in bytes --- merkly/mtree.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/merkly/mtree.py b/merkly/mtree.py index 1530f67..968a13e 100644 --- a/merkly/mtree.py +++ b/merkly/mtree.py @@ -46,7 +46,7 @@ def __repr__(self) -> str: return f"""MerkleTree(\nraw_leafs: {self.raw_leafs}\nleafs: {self.leafs}\nshort_leafs: {self.short(self.leafs)})""" def short(self, data: List[str]) -> List[str]: - return [f"{x[:4]}..." for x in data] + return [x[:2] for x in data] @property def root(self) -> bytes: From d525b9950bd8a30d4daf22704d493f80b4e4e189 Mon Sep 17 00:00:00 2001 From: Lucas Oliveira Date: Sun, 24 Dec 2023 15:36:55 -0300 Subject: [PATCH 16/17] add: BREACK CHANGE - chage way of build merkle root to support compatibility with merkletreejs like explain in #21 --- merkly/mtree.py | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/merkly/mtree.py b/merkly/mtree.py index 968a13e..43a985e 100644 --- a/merkly/mtree.py +++ b/merkly/mtree.py @@ -80,16 +80,18 @@ def concat_nodes(left: Node, right: Node) -> Node: return reduce(concat_nodes, full_proof).data == self.root - def make_root(self, leafs: List[str]) -> List[str]: - if len(leafs) == 1: - return leafs - - return self.make_root( - [ - self.hash_function(pair[0], pair[1]) if len(pair) > 1 else pair[0] - for pair in slice_in_pairs(leafs) - ] - ) + def make_root(self, leafs: List[bytes]) -> List[str]: + while len(leafs) > 1: + next_level = [] + for i in range(0, len(leafs) - 1, 2): + next_level.append(self.hash_function(leafs[i], leafs[i + 1])) + + if len(leafs) % 2 == 1: + next_level.append(leafs[-1]) + + leafs = next_level + + return leafs[0] def make_proof( self, leafs: List[bytes], proof: List[Node], leaf: bytes @@ -130,10 +132,10 @@ def make_proof( left, right = half(leafs) if index < len(leafs) / 2: - proof.append(Node(data=self.make_root(right)[0], side=Side.RIGHT)) + proof.append(Node(data=self.make_root(right), side=Side.RIGHT)) return self.make_proof(left, proof, leaf) else: - proof.append(Node(data=self.make_root(left)[0], side=Side.LEFT)) + proof.append(Node(data=self.make_root(left), side=Side.LEFT)) return self.make_proof(right, proof, leaf) def mix_tree( From a045a1803f5873af67e4d4efdf472aaee0d92402 Mon Sep 17 00:00:00 2001 From: Lucas Oliveira Date: Sun, 24 Dec 2023 15:56:40 -0300 Subject: [PATCH 17/17] fix: installation js deps --- .github/workflows/ci.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e3129c2..7df6703 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -33,6 +33,14 @@ jobs: pip install pytest pip install -e . if [ -f requirements.txt ]; then pip install -r requirements.txt; fi + - name: Set up Node.js + uses: actions/setup-node@v2 + with: + node-version: "18" + - name: Install Node.js dependencies + run: | + cd test/merkletreejs + yarn install - name: Test with pytest run: | pytest -m "not benchmark" -vv