Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: mekletreejs compatibility #34

Closed
wants to merge 19 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
node_modules
.hypothesis
.vscode
*cache*
Expand Down
44 changes: 25 additions & 19 deletions merkly/mtree.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,23 +40,25 @@ def __init__(
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.encode(), b""), 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)})"""

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:
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:
Expand All @@ -78,18 +80,22 @@ 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
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]))

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)
]
)
if len(leafs) % 2 == 1:
next_level.append(leafs[-1])

leafs = next_level

def make_proof(self, leafs: List[str], proof: List[Node], leaf: str) -> List[Node]:
return leafs[0]

def make_proof(
self, leafs: List[bytes], proof: List[Node], leaf: bytes
) -> List[Node]:
"""
# Make a proof

Expand Down Expand Up @@ -126,14 +132,14 @@ def make_proof(self, leafs: List[str], proof: List[Node], leaf: str) -> List[Nod
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(
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
Expand All @@ -148,7 +154,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:
Expand Down
6 changes: 4 additions & 2 deletions merkly/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ 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)


Expand Down Expand Up @@ -89,7 +89,9 @@ def slice_in_pairs(list_item: list):


def validate_leafs(leafs: List[str]):
if len(leafs) < 2:
size = len(leafs)

if size < 2:
raise Exception("Invalid size, need > 2")

a = isinstance(leafs, List)
Expand Down
27 changes: 27 additions & 0 deletions test/errors/test_errors.py
Original file line number Diff line number Diff line change
@@ -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,
)
9 changes: 9 additions & 0 deletions test/merkletreejs/merkle_proof/merkle_proof_test.js
Original file line number Diff line number Diff line change
@@ -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()) }))
19 changes: 19 additions & 0 deletions test/merkletreejs/merkle_proof/merkle_proof_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
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)}))
8 changes: 8 additions & 0 deletions test/merkletreejs/merkle_root/merkle_root_test.js
Original file line number Diff line number Diff line change
@@ -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 }));
15 changes: 15 additions & 0 deletions test/merkletreejs/merkle_root/merkle_root_test.py
Original file line number Diff line number Diff line change
@@ -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"]
tree = MerkleTree(leaves, sha256)
root = tree.root.hex()

print(json.dumps({"root": root}))
10 changes: 10 additions & 0 deletions test/merkletreejs/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"name": "merkletreejs",
"version": "1.0.0",
"main": "index.js",
"license": "MIT",
"dependencies": {
"merkletreejs": "^0.3.11",
"web3": "^4.3.0"
}
}
30 changes: 30 additions & 0 deletions test/merkletreejs/test_merkle_proof_compatibility.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
from pytest import mark
import subprocess

Check notice

Code scanning / Bandit

Consider possible security implications associated with the subprocess module. Note test

Consider possible security implications associated with the subprocess module.
import json


@mark.merkletreejs
def test_merkle_proof_compatibility_between_merkletreejs_and_merkly():
result = subprocess.run(["yarn"], check=False)

Check notice

Code scanning / Bandit

Starting a process with a partial executable path Note test

Starting a process with a partial executable path

Check notice

Code scanning / Bandit

subprocess call - check for execution of untrusted input. Note test

subprocess call - check for execution of untrusted input.

assert result.returncode == 0, result.stderr

Check notice

Code scanning / Bandit

Use of assert detected. The enclosed code will be removed when compiling to optimised byte code. Note test

Use of assert detected. The enclosed code will be removed when compiling to optimised byte code.

result_js = subprocess.run(

Check notice

Code scanning / Bandit

Starting a process with a partial executable path Note test

Starting a process with a partial executable path

Check notice

Code scanning / Bandit

subprocess call - check for execution of untrusted input. Note test

subprocess call - check for execution of untrusted input.
["node", "./test/merkletreejs/merkle_proof/merkle_proof_test.js"],
capture_output=True,
text=True,
check=True,
)
assert result_js.returncode == 0, result_js.stderr

Check notice

Code scanning / Bandit

Use of assert detected. The enclosed code will be removed when compiling to optimised byte code. Note test

Use of assert detected. The enclosed code will be removed when compiling to optimised byte code.
data_js = json.loads(result_js.stdout)

result_py = subprocess.run(

Check notice

Code scanning / Bandit

Starting a process with a partial executable path Note test

Starting a process with a partial executable path

Check notice

Code scanning / Bandit

subprocess call - check for execution of untrusted input. Note test

subprocess call - check for execution of untrusted input.
["python", "./test/merkletreejs/merkle_proof/merkle_proof_test.py"],
capture_output=True,
text=True,
check=True,
)
assert result_py.returncode == 0, result_py.stderr

Check notice

Code scanning / Bandit

Use of assert detected. The enclosed code will be removed when compiling to optimised byte code. Note test

Use of assert detected. The enclosed code will be removed when compiling to optimised byte code.
data_py = json.loads(result_py.stdout)

assert data_js == data_py

Check notice

Code scanning / Bandit

Use of assert detected. The enclosed code will be removed when compiling to optimised byte code. Note test

Use of assert detected. The enclosed code will be removed when compiling to optimised byte code.
30 changes: 30 additions & 0 deletions test/merkletreejs/test_merkle_root_compatibility.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
from pytest import mark
import subprocess

Check notice

Code scanning / Bandit

Consider possible security implications associated with the subprocess module. Note test

Consider possible security implications associated with the subprocess module.
import json


@mark.merkletreejs
def test_merkle_root_compatibility_between_merkletreejs_and_merkly():
result = subprocess.run(["yarn"], check=False)

Check notice

Code scanning / Bandit

Starting a process with a partial executable path Note test

Starting a process with a partial executable path

Check notice

Code scanning / Bandit

subprocess call - check for execution of untrusted input. Note test

subprocess call - check for execution of untrusted input.

assert result.returncode == 0, result.stderr

Check notice

Code scanning / Bandit

Use of assert detected. The enclosed code will be removed when compiling to optimised byte code. Note test

Use of assert detected. The enclosed code will be removed when compiling to optimised byte code.

result_js = subprocess.run(

Check notice

Code scanning / Bandit

Starting a process with a partial executable path Note test

Starting a process with a partial executable path

Check notice

Code scanning / Bandit

subprocess call - check for execution of untrusted input. Note test

subprocess call - check for execution of untrusted input.
["node", "./test/merkletreejs/merkle_root/merkle_root_test.js"],
capture_output=True,
text=True,
check=False,
)
assert result_js.returncode == 0, result_js.stderr

Check notice

Code scanning / Bandit

Use of assert detected. The enclosed code will be removed when compiling to optimised byte code. Note test

Use of assert detected. The enclosed code will be removed when compiling to optimised byte code.
merkle_root_js = json.loads(result_js.stdout)

result_py = subprocess.run(

Check notice

Code scanning / Bandit

Starting a process with a partial executable path Note test

Starting a process with a partial executable path

Check notice

Code scanning / Bandit

subprocess call - check for execution of untrusted input. Note test

subprocess call - check for execution of untrusted input.
["python", "./test/merkletreejs/merkle_root/merkle_root_test.py"],
capture_output=True,
text=True,
check=False,
)
assert result_py.returncode == 0, result_py.stderr

Check notice

Code scanning / Bandit

Use of assert detected. The enclosed code will be removed when compiling to optimised byte code. Note test

Use of assert detected. The enclosed code will be removed when compiling to optimised byte code.
merkle_root_py = json.loads(result_py.stdout)

assert merkle_root_js == merkle_root_py

Check notice

Code scanning / Bandit

Use of assert detected. The enclosed code will be removed when compiling to optimised byte code. Note test

Use of assert detected. The enclosed code will be removed when compiling to optimised byte code.
Loading