Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Signing and Verifying on All Chains #136

Open
wants to merge 4 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions gui/qt/main_window.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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"))
Expand Down Expand Up @@ -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)
Expand Down
22 changes: 6 additions & 16 deletions lib/bitcoin.py
Original file line number Diff line number Diff line change
Expand Up @@ -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."""
Expand Down
54 changes: 45 additions & 9 deletions lib/eckey.py
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down Expand Up @@ -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):
Expand Down Expand Up @@ -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):
Expand Down Expand Up @@ -253,20 +285,21 @@ 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')

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:
Expand All @@ -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")

Expand All @@ -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")

Expand Down
47 changes: 43 additions & 4 deletions lib/tests/test_chains.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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()
Expand All @@ -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")

2 changes: 1 addition & 1 deletion lib/wallet.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down