ghost-preclaims/forester.py
Uncle Stretch e00b5ad867
initial commit
Signed-off-by: Uncle Stretch <uncle.stretch@ghostchain.io>
2026-09-01 13:11:06 +03:00

149 lines
4.3 KiB
Python

import os
import json
import argparse
import sys
from eth_hash.auto import keccak
def parse_arguments():
parser = argparse.ArgumentParser(description="Dynamic Merkle Tree for Ghost Preclaims")
parser.add_argument(
"--network-id",
type=int,
required=True,
help="EVM numeric Network ID to mix into preimage (e.g., 11155111)"
)
return parser.parse_args()
def keccak256(data: bytes) -> bytes:
return keccak(data)
EMPTY_HASH = keccak256(b'')
def next_power_of_two(n: int) -> int:
if n <= 1:
return 1
return 1 << (n - 1).bit_length()
def generate_preimage(token_id, evm_address, shares, network_id) -> bytes:
token_bytes = token_id.to_bytes(32, byteorder='big')
shares_bytes = shares.to_bytes(32, byteorder='big')
clean_addr = evm_address.replace('0x', '')
raw_addr_bytes = bytes.fromhex(clean_addr)
addr_bytes = b'\x00' * 12 + raw_addr_bytes
network_bytes = network_id.to_bytes(32, byteorder='big')
return token_bytes + shares_bytes + addr_bytes + network_bytes
def generate_tree(raw_values, network_id):
max_index = max(item['index'] for item in raw_values)
num_of_leaves = max_index + 1
padded_leaves = next_power_of_two(num_of_leaves)
total_capacity = (2 * padded_leaves) - 1
merkle_tree = [EMPTY_HASH] * total_capacity
for item in raw_values:
idx = item['index']
preimage = generate_preimage(item['token_id'], item['address'], item['shares'], network_id)
merkle_tree[idx] = keccak256(preimage)
layer_start = 0
current_layer_len = padded_leaves
write_ptr = padded_leaves
while current_layer_len > 1:
for i in range(0, current_layer_len, 2):
layer_index = layer_start + i
left_bytes = merkle_tree[layer_index]
right_bytes = merkle_tree[layer_index | 1]
combined = left_bytes + right_bytes
merkle_tree[write_ptr] = keccak256(combined)
write_ptr += 1
layer_start += current_layer_len
current_layer_len >>= 1
return merkle_tree, padded_leaves
def get_merkle_proof(tree, index, padded_leaves):
proof = []
current_idx = index
layer_start = 0
current_layer_len = padded_leaves
while current_layer_len > 1:
sibling_idx = current_idx ^ 1
proof.append(tree[sibling_idx].hex())
local_pair_idx = (current_idx - layer_start) // 2
next_layer_start = layer_start + current_layer_len
current_idx = next_layer_start + local_pair_idx
layer_start = next_layer_start
current_layer_len >>= 1
return proof
def main():
args = parse_arguments()
network_id = args.network_id
snapshot_file = os.path.join("snapshots", f"{network_id}.json")
if not os.path.exists(snapshot_file):
print(f"[-] File not found: {snapshot_file}")
sys.exit(1)
print(f"[+] Reading data snapshot: {snapshot_file}")
with open(snapshot_file, "r") as f:
snapshot_data = json.load(f)
if not snapshot_data:
print("[-] Snapshot file is empty.")
sys.exit(1)
total_shares = 0
for data in snapshot_data:
total_shares += data["shares"]
tree, padded_leaves = generate_tree(snapshot_data, args.network_id)
root_hash = tree[-1].hex()
proof_len = 0
output_packages = {}
for item in snapshot_data:
proof = get_merkle_proof(tree, item['index'], padded_leaves)
proof_len = len(proof)
output_packages[item['address']] = {
"index": item["index"],
"token_id": item['token_id'],
"shares": item['shares'],
"merkle_proof": [f"0x{p}" for p in proof]
}
message = f"=== FINAL MERKLE ROOT [{network_id}]: {root_hash} ==="
separator = "=" * len(message)
print(f"\n{separator}")
print(message)
print(f"{separator}\n")
print(f"[+] Total padded leaves: {padded_leaves}")
print(f"[+] Merkle tree size: {len(tree)}")
print(f"[+] Proof size: {proof_len}")
print(f"[+] Total shares: {total_shares}")
output_file = os.path.join("preclaims", f"{network_id}.json")
os.makedirs(os.path.dirname(output_file), exist_ok=True)
with open(output_file, "w") as f:
json.dump(output_packages, f, indent=2)
print(f"\n[+] Proof are stored into: {output_file}")
if __name__ == "__main__":
main()