Generate merkle trees, compute root hashes, and validate cryptographic proofs for file integrity on 0G Storage.
Every file on 0G Storage is identified by its merkle root hash — a 32-byte fingerprint that binds every byte in the file to a single on-chain commitment. This page covers how to build merkle trees, generate proofs for individual data segments, and validate them against a root hash.The Python SDK produces merkle roots that are byte-exact identical to the TypeScript SDK — files uploaded with either SDK are interchangeable.
from core.merkle import MerkleTreetree = MerkleTree()# Add leaves by hashing raw contenttree.add_leaf(b"chunk 1 data")tree.add_leaf(b"chunk 2 data")tree.add_leaf(b"chunk 3 data")tree.add_leaf(b"chunk 4 data")# Or add leaves by precomputed hash (hex string with 0x prefix)# tree.add_leaf_by_hash("0xabc123...")# Build the tree — required before you can query the root or generate proofsresult = tree.build()if result is None: raise Exception("Tree build failed — empty leaves list?")print(f"Root: {tree.root_hash()}")print(f"Leaves: {len(tree.leaves)}")
tree.build() returns the tree itself (or None if the leaves list is empty). Calling root_hash() before build() returns None.
A merkle proof lets anyone verify that a given chunk belongs to a file without needing the whole file — just the chunk, the root hash, and the proof.
# Generate a proof for the leaf at index 2proof = tree.proof_at(2)print(f"Lemma length: {len(proof.lemma)}") # sibling hashes + rootprint(f"Path length: {len(proof.path)}") # direction bits (True=left side)
proof_at(i) raises IndexError if i is out of range. For a single-leaf tree, the proof has a lemma of [root] and an empty path.
To submit a file to the chain manually (e.g. for batch uploads), use create_submission:
file = ZgFile.from_file_path("./data.txt")submission, err = file.create_submission( tags = b"\x00", submitter = "0xYOUR_WALLET_ADDRESS",)if err is not None: raise err# submission is a dict with 'length', 'tags', 'nodes', 'data', 'submitter'# that you can pass to FlowContract.submit()print(f"Submission nodes: {len(submission['nodes'])}")
This is the same submission structure indexer.upload() builds internally — most users don’t need to call it directly.