proof.sh
1 #!/usr/bin/env bash 2 set -o errexit -o nounset -o pipefail 3 4 # TODO: what about openssl versions? maybe use python for signing? 5 # TODO: tell user how to install utilities 6 7 # This script does the following: 8 # 1. Ask user for her GitHub username and Ethereum address (eth_addr) 9 # 2. Negotiate with user which SSH key to use 10 # 3. Find at least one match of key and encrypted_key that decrypts succesfully 11 # 4. Decrypt encrypted_key to tmp_eth_key 12 # 5. Sign sender ethereum address: sign[tmp_eth_key](eth_addr) 13 # 6. Encode signature (#5) and merkle proof and output result 14 15 # keys.bin format (CSV): 16 # GH UserName,Encrypted[userId,tmp_eth_addr,tmp_eth_key,merkle proof] 17 18 trap 'echo GOT IT ; exit 0' SIGTERM 19 20 # check_program_in_path "program" 21 check_program_in_path() { 22 program="${1}" 23 if ! type -p "${program}" &>/dev/null; then 24 printf '%s\n' "error: ${program} is not installed." 25 printf '%s\n' "You should run install script first" 26 printf '%s\n' "or use your package manager to install it." 27 exit 1 28 fi 29 } 30 31 # while true; do :; done 32 33 # check that everything installed 34 PATH="./bin:${PATH}" 35 for i in age base64 sha3sum; do 36 check_program_in_path $i 37 done 38 39 SSH_KEYS_DIR="$HOME/.ssh" 40 41 ask_ssh_key() { 42 SSH_KEYS=() 43 # list all files from ~/.ssh, except for *.pub, known_hosts, config and log files (tmux sometimes puts logs there) 44 while IFS= read -r -d $'\0'; do 45 SSH_KEYS+=("$REPLY") 46 done < <(find "$SSH_KEYS_DIR" -mindepth 1 -maxdepth 1 ! -name "*.pub" ! -name "known_hosts*" ! -name "config" ! -name "*.log" -print0) 47 48 select fname in "${SSH_KEYS[@]}"; do 49 echo "$fname" 50 break 51 done 52 } 53 54 WORK_DIR="$(pwd)/__sh_cache__" 55 DECRYPTED_DATA="$WORK_DIR/decrypted.data" 56 ETH_KEY_DER="$WORK_DIR/tmp_eth.key.der" 57 ETH_KEY="$WORK_DIR/tmp_eth.key" 58 OPENSSL_STDERR="$WORK_DIR/openssl.stderr" 59 AGE_STDERR="$WORK_DIR/age.stderr" 60 61 mkdir -p $WORK_DIR 62 63 METADATA_BIN="metadata.bin" 64 # $# is the number of arguments 65 if [ $# -gt 1 ]; then 66 GITHUB_USERNAME="$1" 67 ETHEREUM_ADDRESS="$2" 68 else 69 if [ ! -f "$METADATA_BIN" ]; then 70 echo "$METADATA_BIN doesn't exist" 71 exit 1 72 fi 73 74 printf "\nWelcome to the proof generation script for Fluence Developer Reward Airdrop." 75 printf "\n5%% of the FLT supply is allocated to ~110,000 developers who contributed into open source web3 repositories during last year." 76 printf "\nPublic keys of selected Github accounts were added into a smart contract on Ethereum. Claim your allocation and help us build the decentralized internet together!" 77 printf "\n" 78 printf "\nCheck if you are eligible and proceed with claiming" 79 80 read -r -p "Enter your github username so we can check if you are participating in the airdrop: " GITHUB_USERNAME 81 82 printf "\nEthereum wallet address is necessary to generate a proof that you will send through our web page." 83 printf "\n\033[33mImportant notice: you need to make a claim transaction from the entered address!\033[0m\n\n" 84 85 read -r -p "Enter the ethereum address to which you plan to receive the airdrop: " ETHEREUM_ADDRESS 86 87 STR_LENGTH=$(echo "$ETHEREUM_ADDRESS" | sed -e 's/^0x//' | awk '{ print length }') 88 if [ "$STR_LENGTH" -ne 40 ]; then 89 echo "$ETHEREUM_ADDRESS is not an Ethereum address. Must be of 40 or 42 (with 0x) characters, was $STR_LENGTH chars" 90 exit 1 91 fi 92 NON_HEX_BYTES_LENGTH=$(echo "$ETHEREUM_ADDRESS" | sed -e 's/^0x//' | tr -d "[:xdigit:]" | awk '{ print length }') 93 if [ "$NON_HEX_BYTES_LENGTH" -ne 0 ]; then 94 echo "$ETHEREUM_ADDRESS is not an Ethereum address. Must be hexadecimal, has non-hexadecimal symbols." 95 exit 1 96 fi 97 fi 98 99 KEY_ARG_PATH='' 100 if [ $# -gt 2 ]; then 101 KEY_ARG_PATH="$3" 102 fi 103 104 ENCRYPTED_KEYS=() 105 while IFS='' read -r line; do ENCRYPTED_KEYS+=("$line"); done < <(grep -i "^${GITHUB_USERNAME}," "${METADATA_BIN}" || true) 106 107 # ${#ENCRYPTED_KEYS[@]} -- calculates number of elements in the array 108 if [ ${#ENCRYPTED_KEYS[@]} -gt 1 ]; then 109 echo "Found ${#ENCRYPTED_KEYS[@]} encrypted keys for your GitHub username. That means you have several SSH keys published on GitHub" 110 # echo "Any of your keys would work. We have encrypted a temporary keypair for each of your SSH keys." 111 elif [ ${#ENCRYPTED_KEYS[@]} -gt 0 ]; then 112 echo "Found an encrypted key for your GitHub username" 113 else 114 echo "This'$GITHUB_USERNAME' Github account is not eligible for claiming" 115 exit 1 116 fi 117 118 printf "\n\tNOTE: your SSH key is used ONLY LOCALLY to decrypt a message and generate Token Claim Proof." 119 printf "\n\tScript will explicitly ask your consent before using the key." 120 printf "\n\tIf you have any technical issues, take a look at the following logs:\n\t\t$OPENSSL_STDERR\n\t\t$AGE_STDERR\n\tReport any issues to https://fluence.chat \n\n" 121 122 printf "Now the script needs your ssh key to generate proof. \n" 123 124 while true; do 125 if [ -n "$KEY_ARG_PATH" ] && [ -f "$KEY_ARG_PATH" ]; then 126 KEY_PATH=$KEY_ARG_PATH 127 else 128 if [ -d "$SSH_KEYS_DIR" ]; then 129 # shellcheck disable=SC2162 # user can have spaces in the path to ssh key and use backslashes to escape them 130 read -p "Enter path to the private SSH key to use or just press Enter to show existing keys: " KEY_PATH 131 if [ -z "$KEY_PATH" ]; then 132 KEY_PATH=$(ask_ssh_key) 133 fi 134 else 135 # shellcheck disable=SC2162 # user can have spaces in the path to ssh key and use backslashes to escape them 136 read -p "Enter path to the private SSH key to use: " KEY_PATH 137 if [ -z "$KEY_PATH" ]; then 138 continue 139 fi 140 fi 141 142 if ! [ -f "$KEY_PATH" ]; then 143 echo "Specified $KEY_PATH file does not exits or not a SSH private key" 144 continue 145 fi 146 147 read -p "Will use SSH key to generate proof data. Press enter to proceed. " 148 printf "\n" 149 fi 150 151 rm -f "$DECRYPTED_DATA" 152 printf "\n" 153 154 for encrypted in "${ENCRYPTED_KEYS[@]}"; do 155 # contains encrypted (user_id, eth_tmp_key, merkle proof) 156 ENCRYPTED_DATA=$(echo "$encrypted" | cut -d',' -f2) 157 158 set +o errexit 159 echo "$ENCRYPTED_DATA" | xxd -r -p -c 1000 | age --decrypt --identity "$KEY_PATH" --output "$DECRYPTED_DATA" 2>$AGE_STDERR 160 exit_code=$? 161 set -o errexit 162 163 if [ $exit_code -ne 0 ]; then 164 continue 165 else 166 break 167 fi 168 done 169 170 if [ -e "$DECRYPTED_DATA" ]; then 171 # echo "Decrypted succesfully! Decrypted data is at $DECRYPTED_DATA" 172 break 173 else 174 echo "Couldn't decrypt with that SSH key, please choose another one." 175 echo "Possible causes are:" 176 echo "You have specified the file which doesn't contain valid private key." 177 echo "Your private key doesn't match your public key in GitHub. It could happen if you've changed local ssh key recently." 178 echo "Internal error:" 179 180 # replace report URL in $AGE_STDERR 181 STDERR_TMP="$(mktemp)" 182 cat "$AGE_STDERR" | sed -e 's#https://filippo.io/age/report#https://fluence.chat#g' > "$STDERR_TMP" 183 cat "$STDERR_TMP" > "$AGE_STDERR" 184 185 # print Age error with replaced report URL 186 cat "$AGE_STDERR" 187 fi 188 done 189 190 ## Prepare real ethereum address to be hashed and signed 191 ETH_ADDR_HEX_ONLY=$(echo -n "$ETHEREUM_ADDRESS" | sed -e 's/^0x//') 192 # length of ETH key is always 20 bytes 193 LENGTH="20" 194 PREFIX_HEX=$(echo -n $'\x19Ethereum Signed Message:\n'${LENGTH} | xxd -p) 195 DATA_HEX="${PREFIX_HEX}${ETH_ADDR_HEX_ONLY}" 196 197 ## '|| true' is needed to work around this bug https://gitlab.com/kurdy/sha3sum/-/issues/2 198 HASH=$(echo -n "$DATA_HEX" | xxd -r -p | (sha3sum -a Keccak256 -t || true) | sed 's/[^[:xdigit:]].*//') 199 200 ## Write temporary eth key to file in binary format (DER) 201 cat "$DECRYPTED_DATA" | cut -d',' -f3 | xxd -r -p -c 118 >"$ETH_KEY_DER" 202 203 ## Convert secp256k1 key from DER (binary) to textual representation 204 205 set +o errexit 206 openssl ec -inform der -in "$ETH_KEY_DER" 2>$OPENSSL_STDERR >"$ETH_KEY" 207 exit_code=$? 208 set -o errexit 209 210 if [ $exit_code -ne 0 ]; then 211 echo "Failed to parse $ETH_KEY_DER with OpenSSL. Errors below may be relevant." 212 echo "===" 213 cat $OPENSSL_STDERR 214 echo "===" 215 exit 1 216 fi 217 218 ## Sign hash of the real ethereum address with the temporary one 219 SIGNATURE_HEX=$(echo "$HASH" | xxd -r -p | openssl pkeyutl -sign -inkey "$ETH_KEY" | xxd -p -c 72) 220 221 USER_ID=$(cat "$DECRYPTED_DATA" | cut -d',' -f1) 222 TMP_ETH_ADDR=$(cat "$DECRYPTED_DATA" | cut -d',' -f2) 223 MERKLE_PROOF=$(cat "$DECRYPTED_DATA" | cut -d',' -f4) 224 225 echo -e "Success! Copy the line below and paste it in the browser.\n" 226 227 # userId, tmpEthAddr, signatureHex, merkleProofHex 228 echo "${USER_ID},${TMP_ETH_ADDR},${SIGNATURE_HEX},${MERKLE_PROOF}"