diff --git a/gui/qt/main_window.py b/gui/qt/main_window.py index 600fd39c..a4c447d2 100644 --- a/gui/qt/main_window.py +++ b/gui/qt/main_window.py @@ -38,7 +38,7 @@ from chainkey.util import format_satoshis from chainkey import Transaction from chainkey import mnemonic -from chainkey import util, bitcoin, base58, commands, Interface, Wallet +from chainkey import util, bitcoin, eckey, base58, commands, Interface, Wallet from chainkey import SimpleConfig, Wallet, WalletStorage from chainkey import Imported_Wallet import chainkey.chainparams @@ -2092,7 +2092,7 @@ def do_sign(self, address, message, signature, password): def do_verify(self, address, message, signature): message = unicode(message.toPlainText()) message = message.encode('utf-8') - if bitcoin.verify_message(address.text(), str(signature.toPlainText()), message): + if eckey.verify_message(address.text(), str(signature.toPlainText()), message): self.show_message(_("Signature verified")) else: self.show_message(_("Error: wrong signature")) @@ -2152,7 +2152,7 @@ def do_encrypt(self, message_e, pubkey_e, encrypted_e): message = unicode(message_e.toPlainText()) message = message.encode('utf-8') try: - encrypted = bitcoin.encrypt_message(message, str(pubkey_e.text())) + encrypted = eckey.encrypt_message(message, str(pubkey_e.text())) encrypted_e.setText(encrypted) except BaseException as e: traceback.print_exc(file=sys.stdout) diff --git a/lib/bitcoin.py b/lib/bitcoin.py index d8fc9529..a2b84cbd 100644 --- a/lib/bitcoin.py +++ b/lib/bitcoin.py @@ -156,30 +156,20 @@ def ASecretToSecret(key, addrtype=128): return base58.ASecretToSecret(key, addrtype) def regenerate_key(sec, addrtype=128): - b = ASecretToSecret(sec, addrtype) - if not b: - return False - b = b[0:32] - return EC_KEY(b) + """Deprecated.""" + return eckey.regenerate_key(sec, addrtype) def is_compressed(sec, addrtype=128): """Deprecated.""" return base58.is_compressed(sec, addrtype) def public_key_from_private_key(sec, addrtype=128): - """Gets the public key of a WIF private key.""" - # rebuild public key from private key, compressed or uncompressed - pkey = regenerate_key(sec, addrtype) - assert pkey - compressed = is_compressed(sec, addrtype) - public_key = GetPubKey(pkey.pubkey, compressed) - return public_key.encode('hex') + """Deprecated.""" + return eckey.public_key_from_private_key(sec, addrtype) def address_from_private_key(sec, addrtype=0, wif_version=128): - """Gets the address for a WIF private key.""" - public_key = public_key_from_private_key(sec, wif_version) - address = public_key_to_bc_address(public_key.decode('hex'), addrtype) - return address + """Deprecated.""" + return eckey.address_from_private_key(sec, addrtype, wif_version) def is_valid(addr, active_chain=None): """Deprecated.""" diff --git a/lib/eckey.py b/lib/eckey.py index ddde80d4..e7946f6d 100644 --- a/lib/eckey.py +++ b/lib/eckey.py @@ -13,8 +13,11 @@ except ImportError: sys.exit("Error: AES does not seem to be installed. Try 'sudo pip install slowaes'") +from util import print_error from util_coin import var_int, Hash -from base58 import public_key_to_bc_address +import base58 +from base58 import bc_address_to_hash_160, public_key_to_bc_address +import chainparams # AES encryption EncodeAES = lambda secret, s: base64.b64encode(aes.encryptData(secret,s)) @@ -76,6 +79,29 @@ def pw_decode(s, password): ################# code from pywallet ###################### +def regenerate_key(sec, addrtype=128): + """Gets the EC Key represented by a WIF key.""" + b = base58.ASecretToSecret(sec, addrtype) + if not b: + return False + b = b[0:32] + return EC_KEY(b) + +def public_key_from_private_key(sec, addrtype=128): + """Gets the public key of a WIF private key.""" + # rebuild public key from private key, compressed or uncompressed + pkey = regenerate_key(sec, addrtype) + assert pkey + compressed = base58.is_compressed(sec, addrtype) + public_key = GetPubKey(pkey.pubkey, compressed) + return public_key.encode('hex') + +def address_from_private_key(sec, addrtype=0, wif_version=128): + """Gets the address for a WIF private key.""" + public_key = public_key_from_private_key(sec, wif_version) + address = public_key_to_bc_address(public_key.decode('hex'), addrtype) + return address + # pywallet openssl private key implementation def i2d_ECPrivateKey(pkey, compressed=False): @@ -167,10 +193,16 @@ def get_pubkeys_from_secret(secret): exit() -def msg_magic(message): +def msg_magic(message, active_chain=None): + if active_chain is None: + active_chain = chainparams.get_active_chain() varint = var_int(len(message)) encoded_varint = "".join([chr(int(varint[i:i+2], 16)) for i in xrange(0, len(varint), 2)]) - return "\x18Bitcoin Signed Message:\n" + encoded_varint + message + + # Put number of bytes before magic bytes + coin_msg_line = "".join([ active_chain.coin_name, " Signed Message:\n" ]) + coin_msg_line = "".join([ hex(len(coin_msg_line))[2:].decode('hex'), coin_msg_line ]) + return coin_msg_line + encoded_varint + message def verify_message(address, signature, message): @@ -253,11 +285,12 @@ def from_signature(klass, sig, recid, h, curve): return klass.from_public_point( Q, curve ) class EC_KEY(object): - def __init__( self, k ): + def __init__( self, k, active_chain=None ): secret = string_to_number(k) self.pubkey = ecdsa.ecdsa.Public_key( generator_secp256k1, generator_secp256k1 * secret ) self.privkey = ecdsa.ecdsa.Private_key( self.pubkey, secret ) self.secret = secret + self.active_chain = active_chain def get_public_key(self, compressed=True): return point_to_ser(self.pubkey.point, compressed).encode('hex') @@ -265,8 +298,8 @@ def get_public_key(self, compressed=True): def sign_message(self, message, compressed, address): private_key = ecdsa.SigningKey.from_secret_exponent( self.secret, curve = SECP256k1 ) public_key = private_key.get_verifying_key() - signature = private_key.sign_digest_deterministic( Hash( msg_magic(message) ), hashfunc=hashlib.sha256, sigencode = ecdsa.util.sigencode_string ) - assert public_key.verify_digest( signature, Hash( msg_magic(message) ), sigdecode = ecdsa.util.sigdecode_string) + signature = private_key.sign_digest_deterministic( Hash( msg_magic(message, self.active_chain) ), hashfunc=hashlib.sha256, sigencode = ecdsa.util.sigencode_string ) + assert public_key.verify_digest( signature, Hash( msg_magic(message, self.active_chain) ), sigdecode = ecdsa.util.sigdecode_string) for i in range(4): sig = base64.b64encode( chr(27 + i + (4 if compressed else 0)) + signature ) try: @@ -278,7 +311,9 @@ def sign_message(self, message, compressed, address): raise Exception("error: cannot sign message") @classmethod - def verify_message(self, address, signature, message): + def verify_message(self, address, signature, message, active_chain=None): + if getattr(self, 'active_chain', None) is not None: + active_chain = getattr(self, 'active_chain') sig = base64.b64decode(signature) if len(sig) != 65: raise Exception("Wrong encoding") @@ -292,14 +327,15 @@ def verify_message(self, address, signature, message): compressed = False recid = nV - 27 - h = Hash( msg_magic(message) ) + h = Hash( msg_magic(message, active_chain) ) public_key = MyVerifyingKey.from_signature( sig[1:], recid, h, curve = SECP256k1 ) # check public key public_key.verify_digest( sig[1:], h, sigdecode = ecdsa.util.sigdecode_string) # check that we get the original signing address - addr = public_key_to_bc_address( point_to_ser(public_key.pubkey.point, compressed) ) + addrtype = bc_address_to_hash_160(address)[0] + addr = public_key_to_bc_address( point_to_ser(public_key.pubkey.point, compressed), addrtype ) if address != addr: raise Exception("Bad signature") diff --git a/lib/tests/test_chains.py b/lib/tests/test_chains.py index 7821ef3b..1150eb80 100644 --- a/lib/tests/test_chains.py +++ b/lib/tests/test_chains.py @@ -4,7 +4,7 @@ import unittest from StringIO import StringIO -from lib import bitcoin +from lib import bitcoin, eckey from lib.bitcoin import bip32_root, bip32_private_derivation from lib.wallet import WalletStorage, NewWallet from lib import chainparams @@ -167,13 +167,13 @@ def test_update_password(self): self.wallet.get_master_private_key("x/", new_password) ) -class ChainsBase58Test(unittest.TestCase): +class ChainsTest(unittest.TestCase): def setUp(self): - super(ChainsBase58Test, self).setUp() + super(ChainsTest, self).setUp() chainparams.set_active_chain('BTC') -class TestChainsBase58(ChainsBase58Test): +class TestChainsBase58(ChainsTest): def setUp(self): super(TestChainsBase58, self).setUp() @@ -196,3 +196,42 @@ def test_wif_encoding(self): addr = bitcoin.address_from_private_key(wif, addrtype = active_chain.p2pkh_version, wif_version = active_chain.wif_version) self.assertEqual('MC3JocFZncr3eL2yDuH5zDnb9is5GUzUJv', addr) +class TestChainsMessages(ChainsTest): + + def test_verify_message(self): + message = 'This is a signed message for purposes of unit testing.' + ## BTC ## + addr = '12maWtqajNngu3XGZahMQKMb6RnqZtMtqk' + sig = 'IGWi8NPeeGRbfm0FncBIp9gRRQbAnuJS9bSGNjRn7puppjV41LDinIehcJ4ZyYxWbFYmLNDN1whLHmtt3eAjpnU=' + + is_ok = eckey.verify_message(addr, sig, message) + self.assertTrue(is_ok, "Bitcoin verification failed") + + ## MZC ## + chainparams.set_active_chain("MZC") + + addr = 'MN46FX5bBJbJNuHA7kJb7b8xEekumLkis7' + sig = 'H/s+ZhZq+0ZrkQEOxmdAopGC62/gNV7/+fFHuJ2znTn6WdQdkvTIM95ic9A5/Hw80uV1DU7bhjsqus9JMt12gFw=' + + is_ok = eckey.verify_message(addr, sig, message) + self.assertTrue(is_ok, "Mazacoin verification failed") + + def test_sign_message(self): + message = 'This is a signed message for purposes of unit testing.' + ## BTC ## + wif = 'L2Wn6DJDuviQJbEPQcLX7RLSSjyapHkT5je9S68hHoKudwRHqJst' + addr = '12maWtqajNngu3XGZahMQKMb6RnqZtMtqk' + + actual_sig = 'IGWi8NPeeGRbfm0FncBIp9gRRQbAnuJS9bSGNjRn7puppjV41LDinIehcJ4ZyYxWbFYmLNDN1whLHmtt3eAjpnU=' + key = eckey.regenerate_key(wif, addrtype = chainparams.get_active_chain().wif_version ) + self.assertEqual(actual_sig, key.sign_message(message, True, addr), "Bitcoin signature is invalid") + + ## LTC ## + chainparams.set_active_chain("LTC") + wif = '6urdDh4wMR1QujsVoMa45fjY84hVzqGL1xN9zFBsmgHk9yhYcKC' + addr = 'LPynMR5snDixkd8eGSiZ7Ryo81QzVkmLhs' + + actual_sig = 'HPUPXr058K/zHoKOAf8qV+LntcpzhiZlwFdY2JLs2SH+jQutKchuadkXwehMOIqkygrVtta+E3wNZi+uZ3+d5F0=' + key = eckey.regenerate_key(wif, addrtype = chainparams.get_active_chain().wif_version ) + self.assertEqual(actual_sig, key.sign_message(message, False, addr), "Litecoin signature is invalid") + diff --git a/lib/wallet.py b/lib/wallet.py index 2e194daf..edb2cfe3 100644 --- a/lib/wallet.py +++ b/lib/wallet.py @@ -424,7 +424,7 @@ def sign_message(self, address, message, password): assert len(keys) == 1 sec = keys[0] key = regenerate_key(sec, self.active_chain.wif_version) - compressed = is_compressed(sec) + compressed = is_compressed(sec, addrtype=self.active_chain.wif_version) return key.sign_message(message, compressed, address) def decrypt_message(self, pubkey, message, password):