From 9a1aaae0df70631e26fceed03a4e2fb5395c7b06 Mon Sep 17 00:00:00 2001 From: six <51x@keemail.me> Date: Fri, 19 Aug 2022 16:07:18 +0200 Subject: [PATCH] Code cleanup and argparse --- LICENSE | 2 +- ethkeygen.py | 147 +++++++++++++++++++++++++++++------------------ requirements.txt | 2 + 3 files changed, 95 insertions(+), 56 deletions(-) create mode 100644 requirements.txt diff --git a/LICENSE b/LICENSE index 137069b..2a19b61 100644 --- a/LICENSE +++ b/LICENSE @@ -58,7 +58,7 @@ APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. -Copyright [yyyy] [name of copyright owner] +Copyright 2022 six (from Crypto CTF OÜ) Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/ethkeygen.py b/ethkeygen.py index 4dca713..d46ceec 100644 --- a/ethkeygen.py +++ b/ethkeygen.py @@ -1,75 +1,112 @@ #!/usr/bin/python3 -# Eth ECDSA key generator for web3 hacking +# Eth ECDSA key generator, signer and arg recovery for web3 hacking # Author: six +# References: https://web3py.readthedocs.io/en/stable/web3.eth.account.html?highlight=sign#sign-a-message +# Ethereum Private Keys Directory -> https://privatekeys.pw/keys/ethereum/1 -# require(owner() == ecrecover(keccak256(abi.encodePacked(this, tokenId)), v, r, s), "Should be signed correctly"); -# https://privatekeys.pw/keys/ethereum/1 +print("\033[38;5;111m\n " + 16*'=' + "\n || EthKeyGen ||\n " + 16*'=' + "\n\033[0;0m") from web3.auto import w3 +from web3 import Web3 from eth_account.messages import encode_defunct -#def sign_mint(): -msg = "0x7E5F4552091A69125d5DfCb7b8C2659029395Bdf" -msg = "1" -msg = "1659023558XXX" # unix time -private_key = "0000000000000000000000000000000000000000000000000000000000000001" # <- hex | pub -> 0x7E5F4552091A69125d5DfCb7b8C2659029395Bdf -message = encode_defunct(text=msg) -signed_message = w3.eth.account.sign_message(message, private_key=private_key) +# Generate signed message and print it out +def generate(_msg, _privkey): + message = encode_defunct(text=_msg) + try: + signed_message = w3.eth.account.sign_message(message, _privkey) + except: + print("Error while signing message. Make sure the private key is in hex and correctly generated.") + sys.exit() + print("\033[38;5;111mPrivate key:\033[0;0m " + _privkey) + print("\033[38;5;111mRecovered signer:\033[0;0m " + w3.eth.account.recover_message(message, signature=signed_message.signature)) + print("\033[38;5;111mMessage str:\033[0;0m " + str(message)) + #print("\033[38;5;111mSigned message:\033[0;0m " + str(signed_message)) + return signed_message -print("Public key: 0x7E5F4552091A69125d5DfCb7b8C2659029395Bdf") -print("Private key: 0000000000000000000000000000000000000000000000000000000000000001") - -print(msg) - -print("Signed message: ") -#print(type(signed_message)) -print(signed_message) - -print("\n Recovered message: ") -print(w3.eth.account.recover_message(message, signature=signed_message.signature)) - -#sign_mint() - - -from web3 import Web3 # ecrecover in Solidity expects v as a native uint8, but r and s as left-padded bytes32 -# Remix / web3.js expect r and s to be encoded to hex -# This convenience method will do the pad & hex for us: +# Remix / web3.js expect r and s to be encoded to hex. This method does the pad & hex: def to_32byte_hex(val): return Web3.toHex(Web3.toBytes(val).rjust(32, b'\0')) -ec_recover_args = (msghash, v, r, s) = ( - Web3.toHex(signed_message.messageHash), - signed_message.v, - to_32byte_hex(signed_message.r), - to_32byte_hex(signed_message.s),) +# Recover for tx +def gen_vrs(signed_message): + ec_recover_args = (msghash, v, r, s) = ( + Web3.toHex(signed_message.messageHash), + signed_message.v, + to_32byte_hex(signed_message.r), + to_32byte_hex(signed_message.s),) + print("\n\033[38;5;111mRecovered message hash:\033[0;0m " + ec_recover_args[0]) + print(" \033[38;5;111mv:\033[0;0m " + str(ec_recover_args[1])) + print(" \033[38;5;111mr:\033[0;0m " + ec_recover_args[2]) + print(" \033[38;5;111ms:\033[0;0m " + ec_recover_args[3]) + print("\033[38;5;111m="*108+"\033[0;0m") -print("\n Recovered args (msghash, v, r, s):") -print(ec_recover_args) +# Generate random string +import string +import random +def random_str(N): + res = ''.join(random.choices(string.ascii_uppercase + string.digits, k=N)) + return(str(res)) -# https://privatekeys.pw/keys/ethereum/1 +# Generate random eth private key +from eth_account import Account +import secrets +def random_privkey(): + priv = secrets.token_hex(32) + private_key = "0x" + priv + acct = Account.from_key(private_key) + return private_key -# https://web3py.readthedocs.io/en/stable/web3.eth.account.html?highlight=sign#sign-a-message -# w3.eth.account.recover_message(message, signature=signed_message.signature) -#'0x5ce9454909639D2D17A3F753ce7d93fa0b9aB12E' +# Solidity code example for verification +def print_sol_recovery(): + print('''// SPDX-License-Identifier: MIT +pragma solidity ^0.8.16; +contract Recover { + function ecr (bytes32 msgh, uint8 v, bytes32 r, bytes32 s) public pure + returns (address sender) { + return ecrecover(msgh, v, r, s); + } +}''') -from flask import Flask, request +# Take command line arguments for convinience +import argparse +import sys +parser = argparse.ArgumentParser() +parser.add_argument("-d", "--dgen", help="Sign message with default keys", action="store_true") +parser.add_argument("-g", "--gen", help="Sign message, requires -m at least, if no -p is provided then it will be random", action="store_true") +parser.add_argument("-r", "--rgen", help="Sign with random key and msg, provide a number for amount") +parser.add_argument("-p", "--pk", help="Provide private key in hex for signed message generation") +parser.add_argument("-m", "--msg", help="Provide message as string for signed message generation") +parser.add_argument("-s", "--sol", help="Print solidity code for ecrecover", action="store_true") +args = parser.parse_args() -app = Flask(__name__) +if args.dgen: + forv = generate("Anything","0000000000000000000000000000000000000000000000000000000000000001") # hex|0x7E5F4552091A69125d5DfCb7b8C2659029395Bdf + gen_vrs(forv) + sys.exit() +if args.rgen: + if int(args.rgen) > 999999: + print("That is probably too much.") + sys.exit() + for i in range(int(args.rgen)): + forv = generate(random_str(9),random_privkey()) + gen_vrs(forv) + sys.exit() +if args.gen: + _pk = str(args.pk) + if _pk == "None": + _pk = random_privkey() + _msg = str(args.msg) + forv = generate(_msg,_pk) + gen_vrs(forv) + sys.exit() +if args.sol: + print_sol_recovery() + sys.exit() -@app.route('/') -def hello(): - return 'Lets play a game... move to /verify and ? is the private key if: ' - -@app.route('/verify', methods=['GET']) -def search(): - args = request.args - args.get("v", default="", type=str) - print(args) - #0000000000000000000000000000000000000000000000000000000000000001 - return args - -#if __name__ == '__main__': -# app.run(debug=False) +forv = generate("Anything","0000000000000000000000000000000000000000000000000000000000000001") # hex|0x7E5F4552091A69125d5DfCb7b8C2659029395Bdf +gen_vrs(forv) +print("\nNo arguments were provided. You can use --help.") diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..54eeb47 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,2 @@ +web3==5.29.1 +eth-account==0.5.7