Skip to content

Instantly share code, notes, and snippets.

@maxgillett
Created February 4, 2022 18:10
Show Gist options
  • Save maxgillett/d079995836eeb8830f16ccd35099f9b1 to your computer and use it in GitHub Desktop.
Save maxgillett/d079995836eeb8830f16ccd35099f9b1 to your computer and use it in GitHub Desktop.
from typing import (Tuple) # noqa: F401
import numpy as np
from web3 import Web3
from web3.auto import w3
from eth_account.messages import encode_defunct, _hash_eip191_message
from eth_keys import keys
from eth_keys.backends.native.ecdsa import (
ecdsa_raw_recover,
deterministic_generate_k,
)
from eth_keys.backends.native.jacobian import (
fast_multiply,
inv,
)
from eth_utils import (
big_endian_to_int,
int_to_big_endian,
)
from eth._utils.padding import (
pad32,
pad32r,
)
import sympy
from sympy.core.numbers import igcdex
from fastecdsa.point import Point
from fastecdsa.curve import secp256k1
from fastecdsa import keys as ecdsa_keys
P = 2**256 - 4294968273
N = 0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141
G = Point(
0x79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798,
0x483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8,
curve=secp256k1,
)
def encode_raw_public_key(raw_public_key: Tuple[int, int]) -> bytes:
left, right = raw_public_key
return b''.join((
pad32(int_to_big_endian(left)),
pad32(int_to_big_endian(right)),
))
def sqrt(n, p):
"""
Finds the minimum positive integer m such that (m*m) % p == n.
"""
return min(sympy.ntheory.residue_ntheory.sqrt_mod(n, p, all_roots=True))
hex_message = '0x49e299a55346'
message = encode_defunct(hexstr=hex_message)
message_hash = _hash_eip191_message(message)
hex_message_hash = Web3.toHex(message_hash)
# Sign message using web3py API
private_key_bytes = b"\xb2\\}\xb3\x1f\xee\xd9\x12''\xbf\t9\xdcv\x9a\x96VK-\xe4\xc4rm\x03[6\xec\xf1\xe5\xb3d"
private_key = big_endian_to_int(private_key_bytes)
signed_message = w3.eth.account.sign_message(message, private_key=private_key)
sig = Web3.toBytes(signed_message.signature)
# Sign message using eth_keys backend
z = big_endian_to_int(message_hash)
k = deterministic_generate_k(message_hash, private_key_bytes)
r, y = fast_multiply((G.x, G.y), k)
s_raw = inv(k, N) * (z + r * big_endian_to_int(private_key_bytes)) % N
v = 27 + ((y % 2) ^ (0 if s_raw * 2 < N else 1))
s = s_raw if s_raw * 2 < N else N - s_raw
#print("(r, y)", Web3.toHex(r), Web3.toHex(y))
#print(v, Web3.toHex(r), Web3.toHex(s))
# Extract ecrecover arguments
v, hex_r, hex_s = Web3.toInt(sig[-1]), Web3.toHex(sig[:32]), Web3.toHex(sig[32:64])
ec_recover_args = (hex_message_hash, v, hex_r, hex_s)
canonical_v = v - 27
r = big_endian_to_int(Web3.toBytes(hexstr=hex_r))
s = big_endian_to_int(Web3.toBytes(hexstr=hex_s))
vrs = (canonical_v, r, s)
print(ec_recover_args)
# Recover public key using fastecdsa library
pub_key = ecdsa_keys.get_public_key(private_key, secp256k1)
print(pub_key)
# Recover public key using eth_keys API
signature = keys.Signature(vrs=vrs)
public_key = signature.recover_public_key_from_msg_hash(message_hash)
print(public_key.to_hex())
# Recover public key using Jacobian projection
raw_public_key_bytes = ecdsa_raw_recover(Web3.toBytes(message_hash), vrs)
print(Web3.toHex(raw_public_key_bytes))
# Recover public key by deriving R from r
R_y = sqrt(r**3 + 7, P)
R = Point(r, R_y if v == 27 else -R_y, curve=secp256k1)
h_m = big_endian_to_int(message_hash)
x = s*R - h_m*G
r_inv = pow(r, N-2, N)
Q = r_inv * x
print(Web3.toHex(encode_raw_public_key((Q.x, Q.y))))
## Print canonical Ethereum address
#address = public_key.to_canonical_address()
#print(Web3.toHex(address))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment