149 lines
4.3 KiB
Python
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()
|