Move data-signing and data-verifying logic from storage.py to keys.py

This commit is contained in:
Jude Nelson
2017-02-20 21:01:50 -05:00
parent 17588ebf27
commit c00bf826aa

View File

@@ -42,7 +42,12 @@ import fastecdsa
import fastecdsa.curve
import fastecdsa.keys
import fastecdsa.ecdsa
from fastecdsa import _ecdsa
from fastecdsa.util import RFC6979
import hmac
from struct import pack
import hashlib
import base64
import pybitcoin
import bitcoin
import binascii
@@ -64,11 +69,9 @@ class HDWallet(object):
"""
Initialize a hierarchical deterministic wallet with
hex_privkey and get child addresses and private keys
TODO: chain state
"""
def __init__(self, hex_privkey=None, config_path=CONFIG_PATH):
def __init__(self, hex_privkey=None, chaincode='\x00' * 32, config_path=CONFIG_PATH):
"""
If @hex_privkey is given, use that to derive keychain
otherwise, use a new random seed
@@ -78,30 +81,33 @@ class HDWallet(object):
global KEYCHAIN_CACHE
assert hex_privkey
assert len(chaincode) == 32
self.hex_privkey = hex_privkey
self.priv_keychain = None
self.master_address = None
self.child_addresses = None
if KEYCHAIN_CACHE.has_key(str(self.hex_privkey)):
keychain_key = str(self.hex_privkey) + ":" + str(chaincode.encode('hex'))
if KEYCHAIN_CACHE.has_key(keychain_key):
if BLOCKSTACK_TEST:
log.debug("{} keychain is cached".format(self.hex_privkey))
log.debug("{} keychain is cached".format(keychain_key))
self.priv_keychain = KEYCHAIN_CACHE[str(self.hex_privkey)]
self.priv_keychain = KEYCHAIN_CACHE[keychain_key]
else:
if BLOCKSTACK_TEST:
log.debug("{} keychain is NOT cached".format(self.hex_privkey))
log.debug("{} keychain is NOT cached".format(keychain_key))
self.priv_keychain = self.get_priv_keychain(self.hex_privkey)
KEYCHAIN_CACHE[str(self.hex_privkey)] = self.priv_keychain
self.priv_keychain = self.get_priv_keychain(self.hex_privkey, chaincode)
KEYCHAIN_CACHE[keychain_key] = self.priv_keychain
self.master_address = self.get_master_address()
def get_priv_keychain(self, hex_privkey):
def get_priv_keychain(self, hex_privkey, chaincode):
if hex_privkey:
return PrivateKeychain.from_private_key(hex_privkey)
return PrivateKeychain.from_private_key(hex_privkey, chain_path=chaincode)
log.debug('No privatekey given, starting new wallet')
return PrivateKeychain()
@@ -217,6 +223,70 @@ class HDWallet(object):
return None
class RFC6979_blockstack(RFC6979):
"""
Generate RFC6979 nonces from a file
"""
def __init__(self, x, q, hashfunc):
RFC6979.__init__(self, '', x, q, hashfunc)
def gen_nonce_from_digest( self, h1 ):
"""
Make the nonce from the digest.
@h1: bin-encoded digest
@hash_size: size of the digest
"""
hash_size = self.hashfunc().digest_size
key_and_msg = self._int2octets(self.x) + self._bits2octets(h1)
v = b''.join([b'\x01' for _ in range(hash_size)])
k = b''.join([b'\x00' for _ in range(hash_size)])
k = hmac.new(k, v + b'\x00' + key_and_msg, self.hashfunc).digest()
v = hmac.new(k, v, self.hashfunc).digest()
k = hmac.new(k, v + b'\x01' + key_and_msg, self.hashfunc).digest()
v = hmac.new(k, v, self.hashfunc).digest()
while True:
t = b''
while len(t) * 8 < self.qlen:
v = hmac.new(k, v, self.hashfunc).digest()
t = t + v
nonce = self._bits2int(t)
if nonce >= 1 and nonce < self.q:
return nonce
k = hmac.new(k, v + b'\x00', self.hashfunc).digest()
v = hmac.new(k, v, self.hashfunc).digest()
def gen_nonce_from_file(self, fd, fd_len=None):
''' http://tools.ietf.org/html/rfc6979#section-3.2 '''
# based on gen_nonce()
h1 = self.hashfunc()
count = 0
while True:
buf = f.read(65536)
if len(buf) == 0:
break
if fd_len is not None:
if count + len(buf) > fd_len:
buf = buf[:fd_len - count]
h.update(buf)
count += len(buf)
h1 = h1.digest()
return self.gen_nonce_from_digest(h1)
def is_multisig(privkey_info):
"""
Does the given private key info represent
@@ -287,7 +357,6 @@ def singlesig_privkey_to_string(privkey_info):
Convert private key to string
"""
return ECPrivateKey(privkey_info).to_hex()
#return virtualchain.BitcoinPrivateKey(privkey_info).to_hex()
def multisig_privkey_to_string(privkey_info):
@@ -723,3 +792,122 @@ def get_uncompressed_private_and_public_keys( privkey_str ):
pubk_hex = virtualchain.BitcoinPrivateKey(pk_hex).public_key().to_hex()
return pk_hex, pubk_hex
def decode_privkey_hex(privkey_hex):
"""
Decode a private key for ecdsa signature
"""
# force uncompressed
priv = str(privkey_hex)
if len(priv) > 64:
assert priv[-2:] == '01'
priv = priv[:64]
pk_i = int(priv, 16)
return pk_i
def decode_pubkey_hex(pubkey_hex):
"""
Decode a public key for ecdsa verification
"""
pubk = str(pubkey_hex)
if keylib.key_formatting.get_pubkey_format(pubk) == 'hex_compressed':
pubk = keylib.key_formatting.decompress(pubk)
assert len(pubk) == 130
pubk_raw = pubk[2:]
pubk_i = (int(pubk_raw[:64], 16), int(pubk_raw[64:], 16))
return pubk_i
def encode_signature(sig_r, sig_s):
"""
Encode an ECDSA signature, with low-s
"""
# enforce low-s
if sig_s * 2 >= fastecdsa.curve.secp256k1.q:
log.debug("High-S to low-S")
sig_s = fastecdsa.curve.secp256k1.q - sig_s
sig_bin = '{:064x}{:064x}'.format(sig_r, sig_s).decode('hex')
assert len(sig_bin) == 64
sig_b64 = base64.b64encode(sig_bin)
return sig_b64
def decode_signature(sigb64):
"""
Decode a signature into r, s
"""
sig_bin = base64.b64decode(sigb64)
assert len(sig_bin) == 64
sig_hex = sig_bin.encode('hex')
sig_r = int(sig_hex[:64], 16)
sig_s = int(sig_hex[64:], 16)
return sig_r, sig_s
def sign_raw_data(raw_data, privatekey_hex):
"""
Sign a string of data.
Returns signature as a base64 string
"""
pk_i = decode_privkey_hex(privatekey_hex)
sig_r, sig_s = fastecdsa.ecdsa.sign(raw_data, pk_i, curve=fastecdsa.curve.secp256k1)
sig_b64 = encode_signature(sig_r, sig_s)
return sig_b64
def verify_raw_data(raw_data, pubkey_hex, sigb64):
"""
Verify the signature over a string, given the public key
and base64-encode signature.
Return True on success.
Return False on error.
"""
sig_r, sig_s = decode_signature(sigb64)
pubk_i = decode_pubkey_hex(pubkey_hex)
res = fastecdsa.ecdsa.verify((sig_r, sig_s), raw_data, pubk_i, curve=fastecdsa.curve.secp256k1)
return res
def sign_digest( digest_hex, privkey_hex, curve=fastecdsa.curve.secp256k1, hashfunc=hashlib.sha256 ):
"""
Sign a digest with ECDSA
Return base64 signature
"""
pk_i = decode_privkey_hex(str(privkey_hex))
# generate a deterministic nonce per RFC6979
rfc6979 = RFC6979_blockstack(pk_i, curve.q, hashfunc)
k = rfc6979.gen_nonce_from_digest(digest_hex.decode('hex'))
r, s = _ecdsa.sign(digest_hex, str(pk_i), str(k), curve.name)
return encode_signature(int(r), int(s))
def verify_digest( digest_hex, pubkey_hex, sigb64, curve=fastecdsa.curve.secp256k1, hashfunc=hashlib.sha256 ):
"""
Verify a digest and signature with ECDSA
Return True if it matches
"""
Q = decode_pubkey_hex(str(pubkey_hex))
r, s = decode_signature(sigb64)
# validate Q, r, s
if not curve.is_point_on_curve(Q):
raise fastecdsa.ecdsa.EcdsaError('Invalid public key, point is not on curve {}'.format(curve.name))
elif r > curve.q or r < 1:
raise fastecdsa.ecdsa.EcdsaError('Invalid Signature: r is not a positive integer smaller than the curve order')
elif s > curve.q or s < 1:
raise fastecdsa.ecdsa.EcdsaError('Invalid Signature: s is not a positive integer smaller than the curve order')
qx, qy = Q
return _ecdsa.verify(str(r), str(s), digest_hex, str(qx), str(qy), curve.name)