Created
August 9, 2016 11:41
-
-
Save xeroc/9bda11add796b603d83eb4b41d38532b to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import struct | |
import time | |
from calendar import timegm | |
from binascii import hexlify, unhexlify | |
import hashlib | |
import ecdsa | |
from steembase.account import PrivateKey | |
from pprint import pprint | |
def varint(n) : | |
""" Varint encoding | |
""" | |
data = b'' | |
while n >= 0x80 : | |
data += bytes([(n & 0x7f) | 0x80]) | |
n >>= 7 | |
data += bytes([n]) | |
return data | |
def recover_public_key(digest, signature, i) : | |
""" Recover the public key from the the signature | |
""" | |
# See http : //www.secg.org/download/aid-780/sec1-v2.pdf section 4.1.6 primarily | |
curve = ecdsa.SECP256k1.curve | |
G = ecdsa.SECP256k1.generator | |
order = ecdsa.SECP256k1.order | |
yp = (i % 2) | |
r, s = ecdsa.util.sigdecode_string(signature, order) | |
# 1.1 | |
x = r + (i // 2) * order | |
# 1.3. This actually calculates for either effectively 02||X or 03||X depending on 'k' instead of always for 02||X as specified. | |
# This substitutes for the lack of reversing R later on. -R actually is defined to be just flipping the y-coordinate in the elliptic curve. | |
alpha = ((x * x * x) + (curve.a() * x) + curve.b()) % curve.p() | |
beta = ecdsa.numbertheory.square_root_mod_prime(alpha, curve.p()) | |
y = beta if (beta - yp) % 2 == 0 else curve.p() - beta | |
# 1.4 Constructor of Point is supposed to check if nR is at infinity. | |
R = ecdsa.ellipticcurve.Point(curve, x, y, order) | |
# 1.5 Compute e | |
e = ecdsa.util.string_to_number(digest) | |
# 1.6 Compute Q = r^-1(sR - eG) | |
Q = ecdsa.numbertheory.inverse_mod(r, order) * (s * R + (-e % order) * G) | |
# Not strictly necessary, but let's verify the message for paranoia's sake. | |
if not ecdsa.VerifyingKey.from_public_point(Q, curve=ecdsa.SECP256k1).verify_digest(signature, digest, sigdecode=ecdsa.util.sigdecode_string) : | |
return None | |
return ecdsa.VerifyingKey.from_public_point(Q, curve=ecdsa.SECP256k1) | |
def recoverPubkeyParameter(digest, signature, pubkey) : | |
""" Use to derive a number that allows to easily recover the | |
public key from the signature | |
""" | |
for i in range(0, 4) : | |
p = recover_public_key(digest, signature, i) | |
if p.to_string() == pubkey.to_string() : | |
return i | |
return None | |
tx = {'ref_block_num': 36029, | |
'ref_block_prefix': 1164960351, | |
'expiration': '2016-08-08T12:24:17', | |
'operations': [['vote', | |
{'author': 'xeroc', | |
'permlink': 'piston', | |
'voter': 'xeroc', | |
'weight': 10000}]], | |
'extensions': [], | |
'signatures': [], | |
} | |
wifs = ["5JLw5dgQAx6rhZEgNN5C2ds1V47RweGshynFSWFbaMohsYsBvE8"] | |
# Operation types | |
operations = {} | |
operations["vote"] = 0 | |
# .... | |
# Internal time format | |
timeformat = '%Y-%m-%dT%H:%M:%S%Z' | |
buf = b"" | |
# ref_block_num | |
buf += struct.pack("<H", tx["ref_block_num"]) | |
print(hexlify(buf)) | |
# ref_block_num | |
buf += struct.pack("<I", tx["ref_block_prefix"]) | |
print(hexlify(buf)) | |
# expiration | |
buf += struct.pack("<I", timegm(time.strptime((tx["expiration"] + "UTC"), timeformat))) | |
print(hexlify(buf)) | |
# Operations | |
buf += bytes(varint(len(tx["operations"]))) | |
print(hexlify(buf)) | |
for op in tx["operations"]: | |
# op[0] == "vote" | |
buf += varint(operations[op[0]]) | |
print(hexlify(buf)) | |
if op[0] == "vote": | |
opdata = op[1] | |
buf += (varint(len(opdata["voter"])) + | |
bytes(opdata["voter"], "utf-8")) | |
print(hexlify(buf)) | |
buf += (varint(len(opdata["author"])) + | |
bytes(opdata["author"], "utf-8")) | |
print(hexlify(buf)) | |
buf += (varint(len(opdata["permlink"])) + | |
bytes(opdata["permlink"], "utf-8")) | |
print(hexlify(buf)) | |
buf += struct.pack("<h", int(opdata["weight"])) | |
print(hexlify(buf)) | |
raise | |
# Signing | |
chainid = "0" * int(256 / 4) | |
message = unhexlify(chainid) + buf | |
digest = hashlib.sha256(message).digest() | |
sigs = [] | |
for wif in wifs: | |
p = bytes(PrivateKey(wif)) # binary representation of private key | |
sk = ecdsa.SigningKey.from_string(p, curve=ecdsa.SECP256k1) | |
cnt = 0 | |
i = 0 | |
while 1 : | |
cnt += 1 | |
# Deterministic k | |
# | |
k = ecdsa.rfc6979.generate_k( | |
sk.curve.generator.order(), | |
sk.privkey.secret_multiplier, | |
hashlib.sha256, | |
hashlib.sha256(digest + bytes([cnt])).digest()) | |
# Sign message | |
# | |
sigder = sk.sign_digest( | |
digest, | |
sigencode=ecdsa.util.sigencode_der, | |
k=k) | |
# Reformating of signature | |
# | |
r, s = ecdsa.util.sigdecode_der(sigder, sk.curve.generator.order()) | |
signature = ecdsa.util.sigencode_string(r, s, sk.curve.generator.order()) | |
# Make sure signature is canonical! | |
# | |
lenR = sigder[3] | |
lenS = sigder[5 + lenR] | |
if lenR is 32 and lenS is 32 : | |
# Derive the recovery parameter | |
# | |
i = recoverPubkeyParameter(digest, signature, sk.get_verifying_key()) | |
i += 4 # compressed | |
i += 27 # compact | |
break | |
tx["signatures"].append( | |
hexlify( | |
struct.pack("<B", i) + | |
signature | |
).decode("ascii") | |
) | |
pprint(tx) | |
from steembase import transactions | |
tx2 = transactions.Signed_Transaction(**tx) | |
tx2.deriveDigest("STEEM") | |
pubkeys = [PrivateKey(p).pubkey for p in wifs] | |
tx2.verify(pubkeys, "STEEM") |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment