|
|
|
|
|
|
|
|
|
|
|
import sys, getopt |
|
|
|
|
|
import json |
|
|
|
|
|
from getpass import getpass |
|
|
|
|
|
from eth_account import Account |
|
|
|
|
|
from hexbytes import HexBytes |
|
|
|
|
|
|
|
|
|
|
|
""" |
|
|
|
|
|
Main logic extracted from: |
|
|
|
|
|
https://github.com/gnosis/gnosis-py |
|
|
|
|
|
""" |
|
|
|
|
|
|
|
|
|
|
|
def signature_to_bytes(v: int, r: int, s: int) -> bytes: |
|
|
|
|
|
""" |
|
|
|
|
|
Convert ecdsa signature to bytes |
|
|
|
|
|
:param v: |
|
|
|
|
|
:param r: |
|
|
|
|
|
:param s: |
|
|
|
|
|
:return: signature in form of {bytes32 r}{bytes32 s}{uint8 v} |
|
|
|
|
|
""" |
|
|
|
|
|
|
|
|
|
|
|
byte_order = "big" |
|
|
|
|
|
|
|
|
|
|
|
return ( |
|
|
|
|
|
r.to_bytes(32, byteorder=byte_order) |
|
|
|
|
|
+ s.to_bytes(32, byteorder=byte_order) |
|
|
|
|
|
+ v.to_bytes(1, byteorder=byte_order) |
|
|
|
|
|
) |
|
|
|
|
|
|
|
|
|
|
|
def sign(private_key: str, safe_tx_hash: HexBytes) -> bytes: |
|
|
|
|
|
""" |
|
|
|
|
|
{bytes32 r}{bytes32 s}{uint8 v} |
|
|
|
|
|
:param private_key: |
|
|
|
|
|
:return: Signature |
|
|
|
|
|
""" |
|
|
|
|
|
account = Account.from_key(private_key) |
|
|
|
|
|
signature_dict = account.signHash(safe_tx_hash) |
|
|
|
|
|
signature = signature_to_bytes( |
|
|
|
|
|
signature_dict["v"], signature_dict["r"], signature_dict["s"] |
|
|
|
|
|
) |
|
|
|
|
|
|
|
|
|
|
|
""" |
|
|
|
|
|
# Insert signature sorted |
|
|
|
|
|
if account.address not in self.signers: |
|
|
|
|
|
new_owners = self.signers + [account.address] |
|
|
|
|
|
new_owner_pos = sorted(new_owners, key=lambda x: int(x, 16)).index( |
|
|
|
|
|
account.address |
|
|
|
|
|
) |
|
|
|
|
|
self.signatures = ( |
|
|
|
|
|
self.signatures[: 65 * new_owner_pos] |
|
|
|
|
|
+ signature |
|
|
|
|
|
+ self.signatures[65 * new_owner_pos :] |
|
|
|
|
|
) |
|
|
|
|
|
""" |
|
|
|
|
|
return signature |
|
|
|
|
|
|
|
|
|
|
|
def get_private_key(keystore: str) -> str: |
|
|
|
|
|
with open(keystore) as f: |
|
|
|
|
|
encrypted = f.readlines() |
|
|
|
|
|
print(encrypted) |
|
|
|
|
|
print(type(encrypted[0])) |
|
|
|
|
|
print(json.loads(encrypted[0])) |
|
|
|
|
|
|
|
|
|
|
|
password = getpass("Enter password:") |
|
|
|
|
|
|
|
|
|
|
|
return Account.decrypt(json.loads(encrypted[0]), password) |
|
|
|
|
|
|
|
|
|
|
|
def trim_tx_hash(safe_tx_hash: str) -> str: |
|
|
|
|
|
if safe_tx_hash[0:2] == "0x": |
|
|
|
|
|
return safe_tx_hash[2:] |
|
|
|
|
|
return safe_tx_hash |
|
|
|
|
|
|
|
|
|
|
|
def get_args(argv): |
|
|
|
|
|
help_string = 'Usage: python3 safe_sign.py -s hash -k keystore' |
|
|
|
|
|
if len(argv) < 4: |
|
|
|
|
|
print(help_string) |
|
|
|
|
|
sys.exit(2) |
|
|
|
|
|
try: |
|
|
|
|
|
opts, args = getopt.getopt(argv,"hs:k:",["hash=,keystore="]) |
|
|
|
|
|
except getopt.GetoptError: |
|
|
|
|
|
print(help_string) |
|
|
|
|
|
sys.exit(2) |
|
|
|
|
|
for opt, arg in opts: |
|
|
|
|
|
if opt == '-h': |
|
|
|
|
|
print(help_string) |
|
|
|
|
|
sys.exit() |
|
|
|
|
|
elif opt in ("-s", "--hash"): |
|
|
|
|
|
safe_tx_hash = arg |
|
|
|
|
|
elif opt in ("-k", "--keystore"): |
|
|
|
|
|
keystore = arg |
|
|
|
|
|
|
|
|
|
|
|
return safe_tx_hash, keystore |
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__": |
|
|
|
|
|
safe_tx_hash, keystore = get_args(sys.argv[1:]) |
|
|
|
|
|
safe_tx_hash = trim_tx_hash(safe_tx_hash) |
|
|
|
|
|
pk = get_private_key(keystore) |
|
|
|
|
|
hb = HexBytes(safe_tx_hash) |
|
|
|
|
|
signature = sign(pk, hb) |
|
|
|
|
|
|
|
|
|
|
|
print(HexBytes(signature).hex()) |