proof.py
1 #!/usr/bin/env python 2 import base64 3 import json 4 import os 5 import sys 6 import subprocess 7 from web3 import Web3 8 from pathlib import Path 9 from eth_account.messages import encode_defunct 10 from web3.auto import w3 11 from helpers.common import Metadata 12 from helpers.merkle import MerkleTree 13 14 SSH_KEYS_DIR = os.path.join(Path.home(), ".ssh") 15 BIN_DIR= os.path.join(os.getcwd(), "./bin") 16 IGNORED_SSH_DIR_FILES = ['.DS_Store'] 17 18 # Set PATH for python to find age 19 env = os.environ.copy() 20 env["PATH"] = BIN_DIR + os.pathsep + env["PATH"] 21 22 def error(msg): 23 print(f"\033[31m{msg}\033[0m") 24 sys.exit(1) 25 26 27 def parse_metadata(filename): 28 with open(filename, 'r') as f: 29 return Metadata.from_json(f.read()) 30 31 32 def ask_user_info(metadata): 33 username = input( 34 "Enter your github username so we can check if you are participating in the airdrop:\n") 35 if username not in metadata.encryptedKeys: 36 error("This Github account is not eligible for claiming") 37 38 ethereumAddress = input( 39 ''' 40 Ethereum wallet address is necessary to generate a proof that you will send through our web page. 41 \033[33mImportant notice: you need to make a claim transaction from the entered address!\033[0m 42 43 Enter the ethereum address to which you plan to receive the airdrop: 44 ''') 45 46 if not Web3.is_address(ethereumAddress): 47 error("You entered an incorrect Ethereum address") 48 49 return (username, ethereumAddress) 50 51 52 def choose_ssh_key(): 53 files = [] 54 try: 55 files = os.listdir(SSH_KEYS_DIR) 56 except FileNotFoundError: 57 pass 58 59 sshKeys = [] 60 for f in files: 61 if f in IGNORED_SSH_DIR_FILES: 62 continue 63 64 path = os.path.join(SSH_KEYS_DIR, f) 65 if not is_ssh_key(path): 66 continue 67 sshKeys.append(path) 68 69 if len(sshKeys) > 0: 70 print(f"\nYour ssh keys in ~/.ssh:") 71 for key in sshKeys: 72 print(key) 73 74 sshKeyPath = input( 75 ''' 76 Now the script needs your ssh key to generate proof. Please, enter path for github SSH key: 77 ''') 78 pubKeyPath = sshKeyPath + ".pub" 79 80 if not os.path.exists(sshKeyPath): 81 error("Specified file does not exits") 82 elif not is_ssh_key(sshKeyPath): 83 error("Specified file is not a SSH private key") 84 elif not os.path.isfile(pubKeyPath) or not os.path.exists(pubKeyPath): 85 error( 86 f"SSH public key ({pubKeyPath}) does not exist in current directory") 87 88 pubKey = "" 89 with open(pubKeyPath, 'r') as pubKeyFile: 90 pubKey = " ".join(pubKeyFile.read().split(" ")[0:2]) 91 92 return pubKey.strip(), sshKeyPath 93 94 95 def is_ssh_key(path): 96 if not os.path.isfile(path): 97 return False 98 99 with open(path, 'r') as file: 100 try: 101 key = file.read() 102 return key.startswith("-----BEGIN") 103 except Exception as e: 104 print("Couldn't read the file:", file, "Reason:", e) 105 return False 106 107 108 def decrypt_temp_eth_account(sshPubKey, sshPrivKey, username, metadata): 109 if sshPubKey not in metadata.encryptedKeys[username]: 110 error("Specified SSH key is not eligible for claiming. Only RSA and Ed25519 keys added before our Github snapshot are supported for proof generation.") 111 112 data = metadata.encryptedKeys[username][sshPubKey] 113 result = subprocess.run(["age", 114 "--decrypt", 115 "--identity", 116 sshPrivKey], 117 capture_output=True, 118 input=data.encode(), 119 env=env) 120 if result.returncode != 0: 121 age_stderr = result.stderr.replace('https://filippo.io/age/report', 'https://fluence.chat') 122 raise OSError(age_stderr) 123 124 return w3.eth.account.from_key(result.stdout.decode()) 125 126 127 def get_merkle_proof(metadata, tempETHAccount): 128 address = tempETHAccount.address.lower() 129 if address not in metadata.addresses: 130 raise ValueError("Invalid temp address") 131 132 tree = MerkleTree(metadata.addresses) 133 index = metadata.addresses.index(address) 134 return index, tree.get_proof(index) 135 136 137 def main(): 138 metadataPath = "metadata.json" 139 metadata = parse_metadata(metadataPath) 140 141 print(''' 142 Welcome to the proof generation script for Fluence Developer Reward Airdrop. 143 5% of the FLT supply is allocated to ~110,000 developers who contributed into open source web3 repositories during last year. 144 Public keys of selected Github accounts were added into a smart contract on Ethereum. Claim your allocation and help us build the decentralized internet together! 145 146 Check if you are eligible and proceed with claiming 147 ''') 148 149 username, receiverAddress = ask_user_info(metadata) 150 sshPubKey, sshKeyPath = choose_ssh_key() 151 tempETHAccount = decrypt_temp_eth_account( 152 sshPubKey, sshKeyPath, username, metadata 153 ) 154 index, merkleProof = get_merkle_proof(metadata, tempETHAccount) 155 base64MerkleProof = base64.b64encode( 156 json.dumps(merkleProof).encode() 157 ).decode() 158 159 sign = tempETHAccount.sign_message(encode_defunct(hexstr=receiverAddress)) 160 161 print("\nSuccess! Copy the line below and paste it in the fluence airdrop website.") 162 print(f"{index},{tempETHAccount.address.lower()},{sign.signature.hex()},{base64MerkleProof}") 163 164 165 if __name__ == '__main__': 166 main()