Skip to content

Instantly share code, notes, and snippets.

@xeroc
Created August 9, 2016 11:41
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save xeroc/9bda11add796b603d83eb4b41d38532b to your computer and use it in GitHub Desktop.
Save xeroc/9bda11add796b603d83eb4b41d38532b to your computer and use it in GitHub Desktop.
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