From 43e49390d85d8e842f8f29d01c674159059fb826 Mon Sep 17 00:00:00 2001 From: Kefkius Date: Fri, 12 Jun 2015 22:25:44 -0400 Subject: [PATCH 1/4] Enable message signing,verifying,encrypting,and decrypting for all chains --- gui/qt/main_window.py | 6 +++--- lib/eckey.py | 5 +++-- lib/wallet.py | 2 +- 3 files changed, 7 insertions(+), 6 deletions(-) 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/eckey.py b/lib/eckey.py index ddde80d4..96b10b46 100644 --- a/lib/eckey.py +++ b/lib/eckey.py @@ -14,7 +14,7 @@ sys.exit("Error: AES does not seem to be installed. Try 'sudo pip install slowaes'") from util_coin import var_int, Hash -from base58 import public_key_to_bc_address +from base58 import bc_address_to_hash_160, public_key_to_bc_address # AES encryption EncodeAES = lambda secret, s: base64.b64encode(aes.encryptData(secret,s)) @@ -299,7 +299,8 @@ def verify_message(self, address, signature, message): 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/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): From 2c96a734d6002d3d2ff8eb013b269d9a0d9c6d9b Mon Sep 17 00:00:00 2001 From: Kefkius Date: Fri, 12 Jun 2015 22:31:36 -0400 Subject: [PATCH 2/4] Move some key-related functions to eckey.py --- lib/bitcoin.py | 22 ++++++---------------- lib/eckey.py | 24 ++++++++++++++++++++++++ 2 files changed, 30 insertions(+), 16 deletions(-) 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 96b10b46..423d920d 100644 --- a/lib/eckey.py +++ b/lib/eckey.py @@ -14,6 +14,7 @@ sys.exit("Error: AES does not seem to be installed. Try 'sudo pip install slowaes'") from util_coin import var_int, Hash +import base58 from base58 import bc_address_to_hash_160, public_key_to_bc_address # AES encryption @@ -76,6 +77,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): From 084fa00798ef1fcc960453e2cdda6cb8c607ce17 Mon Sep 17 00:00:00 2001 From: Kefkius Date: Mon, 13 Jul 2015 11:08:09 -0400 Subject: [PATCH 3/4] Import print_error in eckey.py --- lib/eckey.py | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/eckey.py b/lib/eckey.py index 423d920d..98ece2e8 100644 --- a/lib/eckey.py +++ b/lib/eckey.py @@ -13,6 +13,7 @@ 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 import base58 from base58 import bc_address_to_hash_160, public_key_to_bc_address From e3f18f51dda7818acc4654f2c8941a97945d7c14 Mon Sep 17 00:00:00 2001 From: Kefkius Date: Mon, 13 Jul 2015 12:55:23 -0400 Subject: [PATCH 4/4] Use active chain in message signing/verifying --- lib/eckey.py | 24 ++++++++++++++------ lib/tests/test_chains.py | 47 ++++++++++++++++++++++++++++++++++++---- 2 files changed, 60 insertions(+), 11 deletions(-) diff --git a/lib/eckey.py b/lib/eckey.py index 98ece2e8..e7946f6d 100644 --- a/lib/eckey.py +++ b/lib/eckey.py @@ -17,6 +17,7 @@ from util_coin import var_int, Hash 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)) @@ -192,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): @@ -278,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') @@ -290,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: @@ -303,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") @@ -317,7 +327,7 @@ 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 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") +