mirror of
https://github.com/alexgo-io/stacks-puppet-node.git
synced 2026-03-28 23:58:29 +08:00
Move data-signing and data-verifying logic from storage.py to keys.py
This commit is contained in:
@@ -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)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user