From f0666e6544d34fbdc8309fb8a917a2a4ed02e44e Mon Sep 17 00:00:00 2001
From: Jonathan Warren
Date: Wed, 16 Jan 2013 11:52:52 -0500
Subject: [PATCH] some address generation work completed
---
addresses.py | 29 ++-
bitmessagemain.py | 226 ++++++++++++----
newaddressdialog.py | 151 +++++++++--
newaddressdialog.ui | 476 +++++++++++++++++++++++-----------
pyelliptic/__init__.py | 17 ++
pyelliptic/arithmetic.py | 103 ++++++++
pyelliptic/cipher.py | 81 ++++++
pyelliptic/ecc.py | 460 ++++++++++++++++++++++++++++++++
pyelliptic/hash.py | 43 +++
pyelliptic/highlevelcrypto.py | 33 +++
pyelliptic/openssl.py | 422 ++++++++++++++++++++++++++++++
11 files changed, 1809 insertions(+), 232 deletions(-)
create mode 100644 pyelliptic/__init__.py
create mode 100644 pyelliptic/arithmetic.py
create mode 100644 pyelliptic/cipher.py
create mode 100644 pyelliptic/ecc.py
create mode 100644 pyelliptic/hash.py
create mode 100644 pyelliptic/highlevelcrypto.py
create mode 100644 pyelliptic/openssl.py
diff --git a/addresses.py b/addresses.py
index 60768783..0ad8f5ea 100644
--- a/addresses.py
+++ b/addresses.py
@@ -95,6 +95,12 @@ def calculateInventoryHash(data):
return sha2.digest()[0:32]
def encodeAddress(version,stream,ripe):
+ if version >= 2:
+ if ripe[:2] == '\x00\x00':
+ ripe = ripe[2:]
+ elif ripe[:1] == '\x00':
+ ripe = ripe[1:]
+ print 'within encodeAddress, length of ripe is:', len(ripe)
a = encodeVarint(version) + encodeVarint(stream) + ripe
sha = hashlib.new('sha512')
sha.update(a)
@@ -162,15 +168,30 @@ def decodeAddress(address):
#print 'addressVersionNumber', addressVersionNumber
#print 'bytesUsedByVersionNumber', bytesUsedByVersionNumber
- if addressVersionNumber != 1:
- print 'cannot decode version address version numbers this high'
+ if addressVersionNumber > 2:
+ print 'cannot decode address version numbers this high'
+ status = 'versiontoohigh'
+ return status,0,0,0
+ elif addressVersionNumber == 0:
+ print 'cannot decode address version numbers of zero.'
status = 'versiontoohigh'
return status,0,0,0
- streamNumber, bytesUsedByStreamNumber = decodeVarint(data[bytesUsedByVersionNumber:10+bytesUsedByVersionNumber])
+ streamNumber, bytesUsedByStreamNumber = decodeVarint(data[bytesUsedByVersionNumber:])
#print streamNumber
status = 'success'
- return status,addressVersionNumber,streamNumber,data[-24:-4]
+ if addressVersionNumber == 1:
+ return status,addressVersionNumber,streamNumber,data[-24:-4]
+ elif addressVersionNumber == 2:
+ if len(data[bytesUsedByVersionNumber+bytesUsedByStreamNumber:-4]) == 19:
+ print 'within decode address, lenth I think is 19'
+ return status,addressVersionNumber,streamNumber,'\x00'+data[bytesUsedByVersionNumber+bytesUsedByStreamNumber:-4]
+ elif len(data[bytesUsedByVersionNumber+bytesUsedByStreamNumber:-4]) == 20:
+ print 'within decode address, lenth I think is 20'
+ return status,addressVersionNumber,streamNumber,data[bytesUsedByVersionNumber+bytesUsedByStreamNumber:-4]
+ elif len(data[bytesUsedByVersionNumber+bytesUsedByStreamNumber:-4]) == 18:
+ print 'within decode address, lenth I think is 18'
+ return status,addressVersionNumber,streamNumber,'\x00\x00'+data[bytesUsedByVersionNumber+bytesUsedByStreamNumber:-4]
def addBMIfNotPresent(address):
if address[:3] != 'BM-':
diff --git a/bitmessagemain.py b/bitmessagemain.py
index 136100d6..eee94a8c 100644
--- a/bitmessagemain.py
+++ b/bitmessagemain.py
@@ -48,6 +48,11 @@ from time import strftime, localtime
import os
import string
import socks
+import pyelliptic
+from pyelliptic import highlevelcrypto
+from pyelliptic.openssl import OpenSSL
+import ctypes
+from pyelliptic import arithmetic
#For each stream to which we connect, one outgoingSynSender thread will exist and will create 8 connections with peers.
class outgoingSynSender(QThread):
@@ -64,7 +69,7 @@ class outgoingSynSender(QThread):
time.sleep(1)
resetTime = int(time.time()) #used below to clear out the alreadyAttemptedConnectionsList periodically so that we will retry connecting to hosts to which we have already tried to connect.
while True:
- #time.sleep(999999)#I'm using this to prevent connections for testing.
+ time.sleep(999999)#I'm using this to prevent connections for testing.
if len(self.selfInitiatedConnectionList) < 8: #maximum number of outgoing connections = 8
random.seed()
HOST, = random.sample(knownNodes[self.streamNumber], 1)
@@ -2084,43 +2089,151 @@ class addressGenerator(QThread):
def __init__(self, parent = None):
QThread.__init__(self, parent)
- def setup(self,streamNumber,label):
+ def setup(self,addressVersionNumber,streamNumber,label="(no label)",numberOfAddressesToMake=1,deterministicPassphrase="",eighteenByteRipe=False):
+ self.addressVersionNumber = addressVersionNumber
self.streamNumber = streamNumber
self.label = label
+ self.numberOfAddressesToMake = numberOfAddressesToMake
+ self.deterministicPassphrase = deterministicPassphrase
+ self.eighteenByteRipe = eighteenByteRipe
def run(self):
- statusbar = 'Generating new ' + str(config.getint('bitmessagesettings', 'bitstrength')) + ' bit RSA key. This takes a minute on average. If you want to generate multiple addresses now, you can; they will queue.'
- self.emit(SIGNAL("updateStatusBar(PyQt_PyObject)"),statusbar)
- (pubkey, privkey) = rsa.newkeys(config.getint('bitmessagesettings', 'bitstrength'))
- print privkey['n']
- print privkey['e']
- print privkey['d']
- print privkey['p']
- print privkey['q']
+ if self.addressVersionNumber == 2:
- sha = hashlib.new('sha512')
- #sha.update(str(pubkey.n)+str(pubkey.e))
- sha.update(convertIntToString(pubkey.n)+convertIntToString(pubkey.e))
- ripe = hashlib.new('ripemd160')
- ripe.update(sha.digest())
- address = encodeAddress(1,self.streamNumber,ripe.digest())
+ if self.deterministicPassphrase == "":
+ statusbar = 'Generating one new address'
+ self.emit(SIGNAL("updateStatusBar(PyQt_PyObject)"),statusbar)
+ #This next section is a little bit strange. We're going to generate keys over and over until we
+ #find one that starts with either \x00 or \x00\x00. Then when we pack them into a Bitmessage address,
+ #we won't store the \x00 or \x00\x00 bytes thus making the address shorter.
+ while True:
+ potentialPrivSigningKey = OpenSSL.rand(32)
+ potentialPrivEncryptionKey = OpenSSL.rand(32)
+ potentialPubSigningKey = self.pointMult(potentialPrivSigningKey)
+ potentialPubEncryptionKey = self.pointMult(potentialPrivEncryptionKey)
+ #print 'potentialPubSigningKey', potentialPubSigningKey.encode('hex')
+ #print 'potentialPubEncryptionKey', potentialPubEncryptionKey.encode('hex')
- self.emit(SIGNAL("updateStatusBar(PyQt_PyObject)"),'Finished generating address. Writing to keys.dat')
- config.add_section(address)
- config.set(address,'label',self.label)
- config.set(address,'enabled','true')
- config.set(address,'decoy','false')
- config.set(address,'n',str(privkey['n']))
- config.set(address,'e',str(privkey['e']))
- config.set(address,'d',str(privkey['d']))
- config.set(address,'p',str(privkey['p']))
- config.set(address,'q',str(privkey['q']))
- with open(appdata + 'keys.dat', 'wb') as configfile:
- config.write(configfile)
+ ripe = hashlib.new('ripemd160')
+ sha = hashlib.new('sha512')
+ sha.update(potentialPubSigningKey+potentialPubEncryptionKey)
+ ripe.update(sha.digest())
+ #print 'potential ripe.digest', ripe.digest().encode('hex')
+ if self.eighteenByteRipe:
+ if ripe.digest()[:2] == '\x00\x00':
+ break
+ else:
+ if ripe.digest()[:1] == '\x00':
+ break
+ print 'ripe.digest', ripe.digest().encode('hex')
+ if ripe.digest()[:2] == '\x00\x00':
+ address = encodeAddress(2,self.streamNumber,ripe.digest()[2:])
+ print 'address has 18 byte ripe:', address
+ elif ripe.digest()[:1] == '\x00':
+ address = encodeAddress(2,self.streamNumber,ripe.digest()[1:])
+ print 'address has 19 byte ripe:', address
+ self.emit(SIGNAL("updateStatusBar(PyQt_PyObject)"),'Finished generating address. Writing to keys.dat')
+ status,addressVersionNumber,streamNumber,hash = decodeAddress(address)
+ print status,addressVersionNumber,streamNumber,hash.encode('hex')
- self.emit(SIGNAL("updateStatusBar(PyQt_PyObject)"),'Done generating address')
- self.emit(SIGNAL("writeNewAddressToTable(PyQt_PyObject,PyQt_PyObject,PyQt_PyObject)"),self.label,address,str(self.streamNumber))
+ #An excellent way for us to store our keys is in Wallet Import Format. Let us convert now.
+ #https://en.bitcoin.it/wiki/Wallet_import_format
+ privSigningKey = '\x80'+potentialPrivSigningKey
+ checksum = hashlib.sha256(hashlib.sha256(privSigningKey).digest()).digest()[0:4]
+ privSigningKeyWIF = arithmetic.changebase(privSigningKey + checksum,256,58)
+ print 'privSigningKeyWIF',privSigningKeyWIF
+ privEncryptionKey = '\x80'+potentialPrivEncryptionKey
+ checksum = hashlib.sha256(hashlib.sha256(privEncryptionKey).digest()).digest()[0:4]
+ privEncryptionKeyWIF = arithmetic.changebase(privEncryptionKey + checksum,256,58)
+ print 'privEncryptionKeyWIF',privEncryptionKeyWIF
+
+ config.add_section(address)
+ print 'self.label', self.label
+ config.set(address,'label',self.label)
+ config.set(address,'enabled','true')
+ config.set(address,'decoy','false')
+ config.set(address,'privSigningKey',privSigningKeyWIF)
+ config.set(address,'privEncryptionKey',privEncryptionKeyWIF)
+ with open(appdata + 'keys.dat', 'wb') as configfile:
+ config.write(configfile)
+
+ self.emit(SIGNAL("updateStatusBar(PyQt_PyObject)"),'Done generating address')
+ self.emit(SIGNAL("writeNewAddressToTable(PyQt_PyObject,PyQt_PyObject,PyQt_PyObject)"),self.label,address,str(self.streamNumber))
+
+
+ else: #There is something in the deterministicPassphrase variable thus we are going to do this deterministically.
+ statusbar = 'Generating '+numberOfAddressesToMake + ' new addresses.'
+ self.emit(SIGNAL("updateStatusBar(PyQt_PyObject)"),statusbar)
+
+ elif self.addressVersionNumber == 1:
+ statusbar = 'Generating new ' + str(config.getint('bitmessagesettings', 'bitstrength')) + ' bit RSA key. This takes a minute on average. If you want to generate multiple addresses now, you can; they will queue.'
+ self.emit(SIGNAL("updateStatusBar(PyQt_PyObject)"),statusbar)
+ (pubkey, privkey) = rsa.newkeys(config.getint('bitmessagesettings', 'bitstrength'))
+ print privkey['n']
+ print privkey['e']
+ print privkey['d']
+ print privkey['p']
+ print privkey['q']
+
+ sha = hashlib.new('sha512')
+ #sha.update(str(pubkey.n)+str(pubkey.e))
+ sha.update(convertIntToString(pubkey.n)+convertIntToString(pubkey.e))
+ ripe = hashlib.new('ripemd160')
+ ripe.update(sha.digest())
+ address = encodeAddress(1,self.streamNumber,ripe.digest())
+
+ self.emit(SIGNAL("updateStatusBar(PyQt_PyObject)"),'Finished generating address. Writing to keys.dat')
+ config.add_section(address)
+ config.set(address,'label',self.label)
+ config.set(address,'enabled','true')
+ config.set(address,'decoy','false')
+ config.set(address,'n',str(privkey['n']))
+ config.set(address,'e',str(privkey['e']))
+ config.set(address,'d',str(privkey['d']))
+ config.set(address,'p',str(privkey['p']))
+ config.set(address,'q',str(privkey['q']))
+ with open(appdata + 'keys.dat', 'wb') as configfile:
+ config.write(configfile)
+
+ self.emit(SIGNAL("updateStatusBar(PyQt_PyObject)"),'Done generating address')
+ self.emit(SIGNAL("writeNewAddressToTable(PyQt_PyObject,PyQt_PyObject,PyQt_PyObject)"),self.label,address,str(self.streamNumber))
+
+ def pointMult(self,secret):
+ #passphrase += 'a'
+ #secret = hashlib.sha256(passphrase.encode('utf8')).digest()
+ #if secret starts with too many FF's, continue
+ ctx = OpenSSL.BN_CTX_new()
+ #secret = OpenSSL.rand(32)
+ k = OpenSSL.EC_KEY_new_by_curve_name(OpenSSL.get_curve('secp256k1'))
+ priv_key = OpenSSL.BN_bin2bn(secret, 32, 0)
+ group = OpenSSL.EC_KEY_get0_group(k)
+ pub_key = OpenSSL.EC_POINT_new(group)
+
+ OpenSSL.EC_POINT_mul(group, pub_key, priv_key, None, None, ctx)
+ OpenSSL.EC_KEY_set_private_key(k, priv_key)
+ OpenSSL.EC_KEY_set_public_key(k, pub_key)
+ #print 'priv_key',priv_key
+ #print 'pub_key',pub_key
+
+ size = OpenSSL.i2o_ECPublicKey(k, 0)
+ mb = ctypes.create_string_buffer(size)
+ OpenSSL.i2o_ECPublicKey(k, ctypes.byref(ctypes.pointer(mb)))
+ #print 'mb.raw', mb.raw.encode('hex'), 'length:', len(mb.raw)
+ #print 'mb.raw', mb.raw, 'length:', len(mb.raw)
+
+
+
+
+ #ripe = hashlib.new('ripemd160')
+ #sha = hashlib.new('sha512')
+ #sha.update(mb.raw)
+ #ripe.update(sha.digest())
+ OpenSSL.EC_POINT_free(pub_key)
+ OpenSSL.BN_CTX_free(ctx)
+ OpenSSL.BN_free(priv_key)
+ OpenSSL.EC_KEY_free(k)
+ return mb.raw
class iconGlossaryDialog(QtGui.QDialog):
def __init__(self,parent):
@@ -2231,8 +2344,8 @@ class NewSubscriptionDialog(QtGui.QDialog):
class NewAddressDialog(QtGui.QDialog):
def __init__(self, parent):
QtGui.QWidget.__init__(self, parent)
- self.ui = Ui_NewAddressDialog() #Jonathan changed this line
- self.ui.setupUi(self) #Jonathan left this line alone
+ self.ui = Ui_NewAddressDialog()
+ self.ui.setupUi(self)
self.parent = parent
row = 1
while self.parent.ui.tableWidgetYourIdentities.item(row-1,1):
@@ -2240,8 +2353,8 @@ class NewAddressDialog(QtGui.QDialog):
#print self.parent.ui.tableWidgetYourIdentities.item(row-1,1).text()
self.ui.comboBoxExisting.addItem(self.parent.ui.tableWidgetYourIdentities.item(row-1,1).text())
row += 1
- #QtGui.QWidget.resize(self,QtGui.QWidget.sizeHint(self))
-
+ self.ui.groupBoxDeterministic.setHidden(True)
+ QtGui.QWidget.resize(self,QtGui.QWidget.sizeHint(self))
class MyForm(QtGui.QMainWindow):
@@ -2541,7 +2654,7 @@ class MyForm(QtGui.QMainWindow):
queryreturn = sqlReturnQueue.get()
for row in queryreturn:
ackdata, = row
- print 'Watching for ackdata', repr(ackdata)
+ print 'Watching for ackdata', ackdata.encode('hex')
ackdataForWhichImWatching[ackdata] = 0
QtCore.QObject.connect(self.ui.tableWidgetYourIdentities, QtCore.SIGNAL("itemChanged(QTableWidgetItem *)"), self.tableWidgetYourIdentitiesItemChanged)
@@ -3225,31 +3338,36 @@ class MyForm(QtGui.QMainWindow):
def click_NewAddressDialog(self):
- print 'click_buttondialog'
self.dialog = NewAddressDialog(self)
-
# For Modal dialogs
if self.dialog.exec_():
- self.dialog.ui.buttonBox.enabled = False
- if self.dialog.ui.radioButtonMostAvailable.isChecked():
- #self.generateAndStoreAnAddress(1)
- streamNumberForAddress = 1
-
+ #self.dialog.ui.buttonBox.enabled = False
+ if self.dialog.ui.radioButtonRandomAddress.isChecked():
+ if self.dialog.ui.radioButtonMostAvailable.isChecked():
+ streamNumberForAddress = 1
+ else:
+ #User selected 'Use the same stream as an existing address.'
+ streamNumberForAddress = addressStream(self.dialog.ui.comboBoxExisting.currentText())
+ self.addressGenerator = addressGenerator()
+ self.addressGenerator.setup(2,streamNumberForAddress,str(self.dialog.ui.newaddresslabel.text().toUtf8()),1,"",self.dialog.ui.checkBoxEighteenByteRipe.isChecked())
+ QtCore.QObject.connect(self.addressGenerator, SIGNAL("writeNewAddressToTable(PyQt_PyObject,PyQt_PyObject,PyQt_PyObject)"), self.writeNewAddressToTable)
+ QtCore.QObject.connect(self.addressGenerator, QtCore.SIGNAL("updateStatusBar(PyQt_PyObject)"), self.updateStatusBar)
+ self.addressGenerator.start()
else:
- #User selected 'Use the same stream as an existing address.'
- streamNumberForAddress = addressStream(self.dialog.ui.comboBoxExisting.currentText())
-
- self.addressGenerator = addressGenerator()
- self.addressGenerator.setup(streamNumberForAddress,str(self.dialog.ui.newaddresslabel.text().toUtf8()))
-
- QtCore.QObject.connect(self.addressGenerator, SIGNAL("writeNewAddressToTable(PyQt_PyObject,PyQt_PyObject,PyQt_PyObject)"), self.writeNewAddressToTable)
- QtCore.QObject.connect(self.addressGenerator, QtCore.SIGNAL("updateStatusBar(PyQt_PyObject)"), self.updateStatusBar)
-
-
- self.addressGenerator.start()
+ if self.dialog.ui.lineEditPassphrase.text() != self.dialog.ui.lineEditPassphraseAgain.text():
+ QMessageBox.about(self, "Passphrase mismatch", "The passphrase you entered twice doesn\'t match. Try again.")
+ elif self.dialog.ui.lineEditPassphrase.text() == "":
+ QMessageBox.about(self, "Choose a passphrase", "You really do need a passphrase.")
+ else:
+ streamNumberForAddress = 1 #this will eventually have to be replaced by logic to determine the most available stream number.
+ self.addressGenerator = addressGenerator()
+ self.addressGenerator.setup(2,streamNumberForAddress,"unused address",self.dialog.ui.spinBoxNumberOfAddressesToMake.value(),self.dialog.ui.lineEditPassphrase.text().toUtf8(),self.dialog.ui.checkBoxEighteenByteRipe.isChecked())
+ QtCore.QObject.connect(self.addressGenerator, SIGNAL("writeNewAddressToTable(PyQt_PyObject,PyQt_PyObject,PyQt_PyObject)"), self.writeNewAddressToTable)
+ QtCore.QObject.connect(self.addressGenerator, QtCore.SIGNAL("updateStatusBar(PyQt_PyObject)"), self.updateStatusBar)
+ self.addressGenerator.start()
else:
- print 'rejected'
+ print 'new address dialog box rejected'
def closeEvent(self, event):
broadcastToSendDataQueues((0, 'shutdown', 'all'))
diff --git a/newaddressdialog.py b/newaddressdialog.py
index 053c83fa..6dd3ca18 100644
--- a/newaddressdialog.py
+++ b/newaddressdialog.py
@@ -2,7 +2,7 @@
# Form implementation generated from reading ui file 'newaddressdialog.ui'
#
-# Created: Wed Dec 19 15:55:07 2012
+# Created: Tue Jan 15 15:21:33 2013
# by: PyQt4 UI code generator 4.9.4
#
# WARNING! All changes made in this file will be lost!
@@ -17,57 +17,154 @@ except AttributeError:
class Ui_NewAddressDialog(object):
def setupUi(self, NewAddressDialog):
NewAddressDialog.setObjectName(_fromUtf8("NewAddressDialog"))
- NewAddressDialog.resize(383, 258)
- self.buttonBox = QtGui.QDialogButtonBox(NewAddressDialog)
- self.buttonBox.setGeometry(QtCore.QRect(160, 220, 201, 32))
- self.buttonBox.setOrientation(QtCore.Qt.Horizontal)
- self.buttonBox.setStandardButtons(QtGui.QDialogButtonBox.Cancel|QtGui.QDialogButtonBox.Ok)
- self.buttonBox.setObjectName(_fromUtf8("buttonBox"))
+ NewAddressDialog.resize(723, 704)
+ self.formLayout = QtGui.QFormLayout(NewAddressDialog)
+ self.formLayout.setFieldGrowthPolicy(QtGui.QFormLayout.AllNonFixedFieldsGrow)
+ self.formLayout.setObjectName(_fromUtf8("formLayout"))
self.label = QtGui.QLabel(NewAddressDialog)
- self.label.setGeometry(QtCore.QRect(10, 0, 361, 41))
self.label.setAlignment(QtCore.Qt.AlignBottom|QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft)
self.label.setWordWrap(True)
self.label.setObjectName(_fromUtf8("label"))
- self.label_2 = QtGui.QLabel(NewAddressDialog)
- self.label_2.setGeometry(QtCore.QRect(20, 50, 301, 20))
+ self.formLayout.setWidget(0, QtGui.QFormLayout.SpanningRole, self.label)
+ self.label_5 = QtGui.QLabel(NewAddressDialog)
+ self.label_5.setWordWrap(True)
+ self.label_5.setObjectName(_fromUtf8("label_5"))
+ self.formLayout.setWidget(2, QtGui.QFormLayout.SpanningRole, self.label_5)
+ self.line = QtGui.QFrame(NewAddressDialog)
+ sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Fixed)
+ sizePolicy.setHorizontalStretch(0)
+ sizePolicy.setVerticalStretch(0)
+ sizePolicy.setHeightForWidth(self.line.sizePolicy().hasHeightForWidth())
+ self.line.setSizePolicy(sizePolicy)
+ self.line.setMinimumSize(QtCore.QSize(100, 2))
+ self.line.setFrameShape(QtGui.QFrame.HLine)
+ self.line.setFrameShadow(QtGui.QFrame.Sunken)
+ self.line.setObjectName(_fromUtf8("line"))
+ self.formLayout.setWidget(4, QtGui.QFormLayout.SpanningRole, self.line)
+ self.radioButtonRandomAddress = QtGui.QRadioButton(NewAddressDialog)
+ self.radioButtonRandomAddress.setChecked(True)
+ self.radioButtonRandomAddress.setObjectName(_fromUtf8("radioButtonRandomAddress"))
+ self.formLayout.setWidget(5, QtGui.QFormLayout.SpanningRole, self.radioButtonRandomAddress)
+ self.radioButtonDeterministicAddress = QtGui.QRadioButton(NewAddressDialog)
+ self.radioButtonDeterministicAddress.setObjectName(_fromUtf8("radioButtonDeterministicAddress"))
+ self.formLayout.setWidget(6, QtGui.QFormLayout.LabelRole, self.radioButtonDeterministicAddress)
+ self.checkBoxEighteenByteRipe = QtGui.QCheckBox(NewAddressDialog)
+ self.checkBoxEighteenByteRipe.setObjectName(_fromUtf8("checkBoxEighteenByteRipe"))
+ self.formLayout.setWidget(9, QtGui.QFormLayout.SpanningRole, self.checkBoxEighteenByteRipe)
+ self.groupBoxDeterministic = QtGui.QGroupBox(NewAddressDialog)
+ self.groupBoxDeterministic.setObjectName(_fromUtf8("groupBoxDeterministic"))
+ self.gridLayout = QtGui.QGridLayout(self.groupBoxDeterministic)
+ self.gridLayout.setObjectName(_fromUtf8("gridLayout"))
+ self.label_6 = QtGui.QLabel(self.groupBoxDeterministic)
+ self.label_6.setObjectName(_fromUtf8("label_6"))
+ self.gridLayout.addWidget(self.label_6, 0, 0, 1, 4)
+ self.lineEditPassphrase = QtGui.QLineEdit(self.groupBoxDeterministic)
+ self.lineEditPassphrase.setInputMethodHints(QtCore.Qt.ImhHiddenText|QtCore.Qt.ImhNoAutoUppercase|QtCore.Qt.ImhNoPredictiveText)
+ self.lineEditPassphrase.setEchoMode(QtGui.QLineEdit.Password)
+ self.lineEditPassphrase.setObjectName(_fromUtf8("lineEditPassphrase"))
+ self.gridLayout.addWidget(self.lineEditPassphrase, 1, 0, 1, 7)
+ self.label_7 = QtGui.QLabel(self.groupBoxDeterministic)
+ self.label_7.setObjectName(_fromUtf8("label_7"))
+ self.gridLayout.addWidget(self.label_7, 2, 0, 1, 4)
+ self.lineEditPassphraseAgain = QtGui.QLineEdit(self.groupBoxDeterministic)
+ self.lineEditPassphraseAgain.setEchoMode(QtGui.QLineEdit.Password)
+ self.lineEditPassphraseAgain.setObjectName(_fromUtf8("lineEditPassphraseAgain"))
+ self.gridLayout.addWidget(self.lineEditPassphraseAgain, 3, 0, 1, 7)
+ self.label_8 = QtGui.QLabel(self.groupBoxDeterministic)
+ self.label_8.setObjectName(_fromUtf8("label_8"))
+ self.gridLayout.addWidget(self.label_8, 4, 0, 1, 7)
+ self.label_9 = QtGui.QLabel(self.groupBoxDeterministic)
+ self.label_9.setObjectName(_fromUtf8("label_9"))
+ self.gridLayout.addWidget(self.label_9, 5, 0, 1, 1)
+ spacerItem = QtGui.QSpacerItem(73, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum)
+ self.gridLayout.addItem(spacerItem, 5, 1, 1, 1)
+ self.label_10 = QtGui.QLabel(self.groupBoxDeterministic)
+ self.label_10.setObjectName(_fromUtf8("label_10"))
+ self.gridLayout.addWidget(self.label_10, 5, 3, 1, 1)
+ spacerItem1 = QtGui.QSpacerItem(42, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum)
+ self.gridLayout.addItem(spacerItem1, 5, 4, 1, 2)
+ self.label_11 = QtGui.QLabel(self.groupBoxDeterministic)
+ self.label_11.setObjectName(_fromUtf8("label_11"))
+ self.gridLayout.addWidget(self.label_11, 6, 0, 1, 5)
+ self.spinBoxNumberOfAddressesToMake = QtGui.QSpinBox(self.groupBoxDeterministic)
+ self.spinBoxNumberOfAddressesToMake.setMinimum(1)
+ self.spinBoxNumberOfAddressesToMake.setProperty("value", 8)
+ self.spinBoxNumberOfAddressesToMake.setObjectName(_fromUtf8("spinBoxNumberOfAddressesToMake"))
+ self.gridLayout.addWidget(self.spinBoxNumberOfAddressesToMake, 6, 5, 1, 2)
+ self.formLayout.setWidget(8, QtGui.QFormLayout.LabelRole, self.groupBoxDeterministic)
+ self.groupBox = QtGui.QGroupBox(NewAddressDialog)
+ self.groupBox.setObjectName(_fromUtf8("groupBox"))
+ self.gridLayout_2 = QtGui.QGridLayout(self.groupBox)
+ self.gridLayout_2.setObjectName(_fromUtf8("gridLayout_2"))
+ self.label_2 = QtGui.QLabel(self.groupBox)
self.label_2.setObjectName(_fromUtf8("label_2"))
- self.newaddresslabel = QtGui.QLineEdit(NewAddressDialog)
- self.newaddresslabel.setGeometry(QtCore.QRect(20, 70, 351, 20))
+ self.gridLayout_2.addWidget(self.label_2, 0, 0, 1, 2)
+ self.newaddresslabel = QtGui.QLineEdit(self.groupBox)
self.newaddresslabel.setObjectName(_fromUtf8("newaddresslabel"))
- self.radioButtonMostAvailable = QtGui.QRadioButton(NewAddressDialog)
- self.radioButtonMostAvailable.setGeometry(QtCore.QRect(20, 110, 401, 16))
- self.radioButtonMostAvailable.setChecked(True)
+ self.gridLayout_2.addWidget(self.newaddresslabel, 1, 0, 1, 2)
+ self.radioButtonMostAvailable = QtGui.QRadioButton(self.groupBox)
+ self.radioButtonMostAvailable.setChecked(False)
self.radioButtonMostAvailable.setObjectName(_fromUtf8("radioButtonMostAvailable"))
- self.radioButtonExisting = QtGui.QRadioButton(NewAddressDialog)
- self.radioButtonExisting.setGeometry(QtCore.QRect(20, 150, 351, 18))
- self.radioButtonExisting.setChecked(False)
- self.radioButtonExisting.setObjectName(_fromUtf8("radioButtonExisting"))
- self.label_3 = QtGui.QLabel(NewAddressDialog)
- self.label_3.setGeometry(QtCore.QRect(35, 127, 351, 20))
+ self.gridLayout_2.addWidget(self.radioButtonMostAvailable, 2, 0, 1, 2)
+ self.label_3 = QtGui.QLabel(self.groupBox)
self.label_3.setAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft|QtCore.Qt.AlignTop)
self.label_3.setObjectName(_fromUtf8("label_3"))
- self.label_4 = QtGui.QLabel(NewAddressDialog)
- self.label_4.setGeometry(QtCore.QRect(37, 167, 351, 21))
+ self.gridLayout_2.addWidget(self.label_3, 3, 1, 1, 1)
+ self.radioButtonExisting = QtGui.QRadioButton(self.groupBox)
+ self.radioButtonExisting.setChecked(False)
+ self.radioButtonExisting.setObjectName(_fromUtf8("radioButtonExisting"))
+ self.gridLayout_2.addWidget(self.radioButtonExisting, 4, 0, 1, 2)
+ self.label_4 = QtGui.QLabel(self.groupBox)
self.label_4.setAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft|QtCore.Qt.AlignTop)
self.label_4.setObjectName(_fromUtf8("label_4"))
- self.comboBoxExisting = QtGui.QComboBox(NewAddressDialog)
+ self.gridLayout_2.addWidget(self.label_4, 5, 1, 1, 1)
+ spacerItem2 = QtGui.QSpacerItem(13, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum)
+ self.gridLayout_2.addItem(spacerItem2, 6, 0, 1, 1)
+ self.comboBoxExisting = QtGui.QComboBox(self.groupBox)
self.comboBoxExisting.setEnabled(False)
- self.comboBoxExisting.setGeometry(QtCore.QRect(40, 190, 331, 22))
self.comboBoxExisting.setEditable(True)
self.comboBoxExisting.setObjectName(_fromUtf8("comboBoxExisting"))
+ self.gridLayout_2.addWidget(self.comboBoxExisting, 6, 1, 1, 1)
+ self.formLayout.setWidget(7, QtGui.QFormLayout.LabelRole, self.groupBox)
+ self.buttonBox = QtGui.QDialogButtonBox(NewAddressDialog)
+ sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.MinimumExpanding, QtGui.QSizePolicy.MinimumExpanding)
+ sizePolicy.setHorizontalStretch(0)
+ sizePolicy.setVerticalStretch(0)
+ sizePolicy.setHeightForWidth(self.buttonBox.sizePolicy().hasHeightForWidth())
+ self.buttonBox.setSizePolicy(sizePolicy)
+ self.buttonBox.setMinimumSize(QtCore.QSize(160, 0))
+ self.buttonBox.setOrientation(QtCore.Qt.Horizontal)
+ self.buttonBox.setStandardButtons(QtGui.QDialogButtonBox.Cancel|QtGui.QDialogButtonBox.Ok)
+ self.buttonBox.setObjectName(_fromUtf8("buttonBox"))
+ self.formLayout.setWidget(10, QtGui.QFormLayout.SpanningRole, self.buttonBox)
self.retranslateUi(NewAddressDialog)
QtCore.QObject.connect(self.buttonBox, QtCore.SIGNAL(_fromUtf8("accepted()")), NewAddressDialog.accept)
QtCore.QObject.connect(self.buttonBox, QtCore.SIGNAL(_fromUtf8("rejected()")), NewAddressDialog.reject)
QtCore.QObject.connect(self.radioButtonExisting, QtCore.SIGNAL(_fromUtf8("toggled(bool)")), self.comboBoxExisting.setEnabled)
+ QtCore.QObject.connect(self.radioButtonDeterministicAddress, QtCore.SIGNAL(_fromUtf8("toggled(bool)")), self.groupBoxDeterministic.setShown)
+ QtCore.QObject.connect(self.radioButtonRandomAddress, QtCore.SIGNAL(_fromUtf8("toggled(bool)")), self.groupBox.setShown)
QtCore.QMetaObject.connectSlotsByName(NewAddressDialog)
def retranslateUi(self, NewAddressDialog):
NewAddressDialog.setWindowTitle(QtGui.QApplication.translate("NewAddressDialog", "Create new Address", None, QtGui.QApplication.UnicodeUTF8))
- self.label.setText(QtGui.QApplication.translate("NewAddressDialog", "Here you may generate as many addresses as you like. Indeed, creating and abandoning addresses is encouraged.", None, QtGui.QApplication.UnicodeUTF8))
+ self.label.setText(QtGui.QApplication.translate("NewAddressDialog", "Here you may generate as many addresses as you like. Indeed, creating and abandoning addresses is encouraged. You may generate addresses by using either random numbers or by using a passphrase. If you use a passphrase, the address is called a \"deterministic\" address.\n"
+"The \'Random Number\' option is selected by default but deterministic addresses have several pros and cons:", None, QtGui.QApplication.UnicodeUTF8))
+ self.label_5.setText(QtGui.QApplication.translate("NewAddressDialog", "
Pros:
You can recreate your addresses on any computer from memory.
You need-not worry about backing up your keys.dat file as long as you can remember your passphrase.
Cons:
You must remember (or write down) your passphrase if you expect to be able to recreate your keys if they are lost.
You must remember the address version number and the stream number along with your passphrase.
If you choose a weak passphrase and someone on the Internet can brute-force it, they can read your messages and send messages as you.
", None, QtGui.QApplication.UnicodeUTF8))
+ self.radioButtonRandomAddress.setText(QtGui.QApplication.translate("NewAddressDialog", "Use a random number generator to make an address", None, QtGui.QApplication.UnicodeUTF8))
+ self.radioButtonDeterministicAddress.setText(QtGui.QApplication.translate("NewAddressDialog", "Use a passpharase to make addresses", None, QtGui.QApplication.UnicodeUTF8))
+ self.checkBoxEighteenByteRipe.setText(QtGui.QApplication.translate("NewAddressDialog", "Spend several minutes of extra computing time to make the address(es) 1 or 2 characters shorter", None, QtGui.QApplication.UnicodeUTF8))
+ self.groupBoxDeterministic.setTitle(QtGui.QApplication.translate("NewAddressDialog", "Make deterministic addresses", None, QtGui.QApplication.UnicodeUTF8))
+ self.label_6.setText(QtGui.QApplication.translate("NewAddressDialog", "Passphrase", None, QtGui.QApplication.UnicodeUTF8))
+ self.label_7.setText(QtGui.QApplication.translate("NewAddressDialog", "Retype passphrase", None, QtGui.QApplication.UnicodeUTF8))
+ self.label_8.setText(QtGui.QApplication.translate("NewAddressDialog", "In addition to your passphrase, you must remember these numbers:", None, QtGui.QApplication.UnicodeUTF8))
+ self.label_9.setText(QtGui.QApplication.translate("NewAddressDialog", "Address version number: 2", None, QtGui.QApplication.UnicodeUTF8))
+ self.label_10.setText(QtGui.QApplication.translate("NewAddressDialog", "Stream number: 1", None, QtGui.QApplication.UnicodeUTF8))
+ self.label_11.setText(QtGui.QApplication.translate("NewAddressDialog", "Number of addresses to make based on your passphrase:", None, QtGui.QApplication.UnicodeUTF8))
+ self.groupBox.setTitle(QtGui.QApplication.translate("NewAddressDialog", "Randomly generate address", None, QtGui.QApplication.UnicodeUTF8))
self.label_2.setText(QtGui.QApplication.translate("NewAddressDialog", "Label (not shown to anyone except you)", None, QtGui.QApplication.UnicodeUTF8))
self.radioButtonMostAvailable.setText(QtGui.QApplication.translate("NewAddressDialog", "Use the most available stream", None, QtGui.QApplication.UnicodeUTF8))
- self.radioButtonExisting.setText(QtGui.QApplication.translate("NewAddressDialog", "Use the same stream as an existing address", None, QtGui.QApplication.UnicodeUTF8))
self.label_3.setText(QtGui.QApplication.translate("NewAddressDialog", " (best if this is the first of many addresses you will create)", None, QtGui.QApplication.UnicodeUTF8))
+ self.radioButtonExisting.setText(QtGui.QApplication.translate("NewAddressDialog", "Use the same stream as an existing address", None, QtGui.QApplication.UnicodeUTF8))
self.label_4.setText(QtGui.QApplication.translate("NewAddressDialog", "(saves you some bandwidth and processing power)", None, QtGui.QApplication.UnicodeUTF8))
diff --git a/newaddressdialog.ui b/newaddressdialog.ui
index c53340ec..7810f174 100644
--- a/newaddressdialog.ui
+++ b/newaddressdialog.ui
@@ -6,151 +6,301 @@
0
0
- 383
- 258
+ 723
+ 704
Create new Address
-
-
-
- 160
- 220
- 201
- 32
-
+
+
+ QFormLayout::AllNonFixedFieldsGrow
-
- Qt::Horizontal
-
-
- QDialogButtonBox::Cancel|QDialogButtonBox::Ok
-
-
-
-
-
- 10
- 0
- 361
- 41
-
-
-
- Here you may generate as many addresses as you like. Indeed, creating and abandoning addresses is encouraged.
-
-
- Qt::AlignBottom|Qt::AlignLeading|Qt::AlignLeft
-
-
- true
-
-
-
-
-
- 20
- 50
- 301
- 20
-
-
-
- Label (not shown to anyone except you)
-
-
-
-
-
- 20
- 70
- 351
- 20
-
-
-
-
-
-
- 20
- 110
- 401
- 16
-
-
-
- Use the most available stream
-
-
- true
-
-
-
-
-
- 20
- 150
- 351
- 18
-
-
-
- Use the same stream as an existing address
-
-
- false
-
-
-
-
-
- 35
- 127
- 351
- 20
-
-
-
- (best if this is the first of many addresses you will create)
-
-
- Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop
-
-
-
-
-
- 37
- 167
- 351
- 21
-
-
-
- (saves you some bandwidth and processing power)
-
-
- Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop
-
-
-
-
- false
-
-
-
- 40
- 190
- 331
- 22
-
-
-
- true
-
-
+ -
+
+
+ Here you may generate as many addresses as you like. Indeed, creating and abandoning addresses is encouraged. You may generate addresses by using either random numbers or by using a passphrase. If you use a passphrase, the address is called a "deterministic" address.
+The 'Random Number' option is selected by default but deterministic addresses have several pros and cons:
+
+
+ Qt::AlignBottom|Qt::AlignLeading|Qt::AlignLeft
+
+
+ true
+
+
+
+ -
+
+
+ <html><head/><body><p><span style=" font-weight:600;">Pros:<br/></span>You can recreate your addresses on any computer from memory. <br/>You need-not worry about backing up your keys.dat file as long as you can remember your passphrase. <br/><span style=" font-weight:600;">Cons:<br/></span>You must remember (or write down) your passphrase if you expect to be able to recreate your keys if they are lost. <br/>You must remember the address version number and the stream number along with your passphrase. <br/>If you choose a weak passphrase and someone on the Internet can brute-force it, they can read your messages and send messages as you.</p></body></html>
+
+
+ true
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 100
+ 2
+
+
+
+ Qt::Horizontal
+
+
+
+ -
+
+
+ Use a random number generator to make an address
+
+
+ true
+
+
+
+ -
+
+
+ Use a passpharase to make addresses
+
+
+
+ -
+
+
+ Spend several minutes of extra computing time to make the address(es) 1 or 2 characters shorter
+
+
+
+ -
+
+
+ Make deterministic addresses
+
+
+
-
+
+
+ Passphrase
+
+
+
+ -
+
+
+ Qt::ImhHiddenText|Qt::ImhNoAutoUppercase|Qt::ImhNoPredictiveText
+
+
+ QLineEdit::Password
+
+
+
+ -
+
+
+ Retype passphrase
+
+
+
+ -
+
+
+ QLineEdit::Password
+
+
+
+ -
+
+
+ In addition to your passphrase, you must remember these numbers:
+
+
+
+ -
+
+
+ Address version number: 2
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ 73
+ 20
+
+
+
+
+ -
+
+
+ Stream number: 1
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ 42
+ 20
+
+
+
+
+ -
+
+
+ Number of addresses to make based on your passphrase:
+
+
+
+ -
+
+
+ 1
+
+
+ 8
+
+
+
+
+
+
+ -
+
+
+ Randomly generate address
+
+
+
-
+
+
+ Label (not shown to anyone except you)
+
+
+
+ -
+
+
+ -
+
+
+ Use the most available stream
+
+
+ false
+
+
+
+ -
+
+
+ (best if this is the first of many addresses you will create)
+
+
+ Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop
+
+
+
+ -
+
+
+ Use the same stream as an existing address
+
+
+ false
+
+
+
+ -
+
+
+ (saves you some bandwidth and processing power)
+
+
+ Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ 13
+ 20
+
+
+
+
+ -
+
+
+ false
+
+
+ true
+
+
+
+
+ comboBoxExisting
+ label_3
+ radioButtonExisting
+ newaddresslabel
+ label_4
+ radioButtonMostAvailable
+ label_2
+ horizontalSpacer_3
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 160
+ 0
+
+
+
+ Qt::Horizontal
+
+
+ QDialogButtonBox::Cancel|QDialogButtonBox::Ok
+
+
+
+
@@ -161,8 +311,8 @@
accept()
- 360
- 234
+ 580
+ 644
157
@@ -177,8 +327,8 @@
reject()
- 360
- 240
+ 580
+ 650
286
@@ -193,12 +343,44 @@
setEnabled(bool)
- 30
- 158
+ 60
+ 349
- 99
- 199
+ 148
+ 394
+
+
+
+
+ radioButtonDeterministicAddress
+ toggled(bool)
+ groupBoxDeterministic
+ setShown(bool)
+
+
+ 92
+ 213
+
+
+ 277
+ 601
+
+
+
+
+ radioButtonRandomAddress
+ toggled(bool)
+ groupBox
+ setShown(bool)
+
+
+ 72
+ 189
+
+
+ 68
+ 268
diff --git a/pyelliptic/__init__.py b/pyelliptic/__init__.py
new file mode 100644
index 00000000..8e9c9c1a
--- /dev/null
+++ b/pyelliptic/__init__.py
@@ -0,0 +1,17 @@
+# Copyright (C) 2010
+# Author: Yann GUIBET
+# Contact:
+
+__version__ = '1.3'
+
+__all__ = [
+ 'OpenSSL',
+ 'ecc',
+ 'cipher',
+ 'hash',
+]
+
+from .openssl import OpenSSL
+from .ecc import ECC
+from .cipher import Cipher
+from .hash import hmac_sha256, hmac_sha512, pbkdf2
diff --git a/pyelliptic/arithmetic.py b/pyelliptic/arithmetic.py
new file mode 100644
index 00000000..f28c060d
--- /dev/null
+++ b/pyelliptic/arithmetic.py
@@ -0,0 +1,103 @@
+import hashlib, re
+
+P = 2**256-2**32-2**9-2**8-2**7-2**6-2**4-1
+A = 0
+Gx = 55066263022277343669578718895168534326250603453777594175500187360389116729240
+Gy = 32670510020758816978083085130507043184471273380659243275938904335757337482424
+G = (Gx,Gy)
+
+def inv(a,n):
+ lm, hm = 1,0
+ low, high = a%n,n
+ while low > 1:
+ r = high/low
+ nm, new = hm-lm*r, high-low*r
+ lm, low, hm, high = nm, new, lm, low
+ return lm % n
+
+def get_code_string(base):
+ if base == 2: return '01'
+ elif base == 10: return '0123456789'
+ elif base == 16: return "0123456789abcdef"
+ elif base == 58: return "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"
+ elif base == 256: return ''.join([chr(x) for x in range(256)])
+ else: raise ValueError("Invalid base!")
+
+def encode(val,base,minlen=0):
+ code_string = get_code_string(base)
+ result = ""
+ while val > 0:
+ result = code_string[val % base] + result
+ val /= base
+ if len(result) < minlen:
+ result = code_string[0]*(minlen-len(result))+result
+ return result
+
+def decode(string,base):
+ code_string = get_code_string(base)
+ result = 0
+ if base == 16: string = string.lower()
+ while len(string) > 0:
+ result *= base
+ result += code_string.find(string[0])
+ string = string[1:]
+ return result
+
+def changebase(string,frm,to,minlen=0):
+ return encode(decode(string,frm),to,minlen)
+
+def base10_add(a,b):
+ if a == None: return b[0],b[1]
+ if b == None: return a[0],a[1]
+ if a[0] == b[0]:
+ if a[1] == b[1]: return base10_double(a[0],a[1])
+ else: return None
+ m = ((b[1]-a[1]) * inv(b[0]-a[0],P)) % P
+ x = (m*m-a[0]-b[0]) % P
+ y = (m*(a[0]-x)-a[1]) % P
+ return (x,y)
+
+def base10_double(a):
+ if a == None: return None
+ m = ((3*a[0]*a[0]+A)*inv(2*a[1],P)) % P
+ x = (m*m-2*a[0]) % P
+ y = (m*(a[0]-x)-a[1]) % P
+ return (x,y)
+
+def base10_multiply(a,n):
+ if n == 0: return G
+ if n == 1: return a
+ if (n%2) == 0: return base10_double(base10_multiply(a,n/2))
+ if (n%2) == 1: return base10_add(base10_double(base10_multiply(a,n/2)),a)
+
+def hex_to_point(h): return (decode(h[2:34],16),decode(h[34:],16))
+
+def point_to_hex(p): return '04'+encode(p[0],16,32)+encode(p[1],16,32)
+
+def multiply(privkey,pubkey):
+ return point_to_hex(base10_multiply(hex_to_point(pubkey),decode(privkey,16)))
+
+def privtopub(privkey):
+ return point_to_hex(base10_multiply(G,decode(privkey,16)))
+
+def add(p1,p2):
+ if (len(p1)==32):
+ return encode(decode(p1,16) + decode(p2,16) % P,16,32)
+ else:
+ return point_to_hex(base10_add(hex_to_point(p1),hex_to_point(p2)))
+
+def hash160(string):
+ intermed = hashlib.sha256(string).digest()
+ return hashlib.new('ripemd160').update(intermed).digest()
+
+def dbl_sha256(string):
+ return hashlib.sha256(hashlib.sha256(string).digest()).digest()
+
+def bin_to_b58check(inp):
+ inp_fmtd = '\x00' + inp
+ leadingzbytes = len(re.match('^\x00*',inp_fmtd).group(0))
+ checksum = dbl_sha256(inp_fmtd)[:4]
+ return '1' * leadingzbytes + changebase(inp_fmtd+checksum,256,58)
+
+def pubkey_to_address(pubkey):
+ return bin_to_b58check(hash_160(changebase(pubkey,16,256)))
diff --git a/pyelliptic/cipher.py b/pyelliptic/cipher.py
new file mode 100644
index 00000000..fb8d0d46
--- /dev/null
+++ b/pyelliptic/cipher.py
@@ -0,0 +1,81 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+# Copyright (C) 2011 Yann GUIBET
+# See LICENSE for details.
+
+from pyelliptic.openssl import OpenSSL
+
+
+class Cipher:
+ """
+ Symmetric encryption
+
+ import pyelliptic
+ iv = pyelliptic.Cipher.gen_IV('aes-256-cfb')
+ ctx = pyelliptic.Cipher("secretkey", iv, 1, ciphername='aes-256-cfb')
+ ciphertext = ctx.update('test1')
+ ciphertext += ctx.update('test2')
+ ciphertext += ctx.final()
+
+ ctx2 = pyelliptic.Cipher("secretkey", iv, 0, ciphername='aes-256-cfb')
+ print ctx2.ciphering(ciphertext)
+ """
+ def __init__(self, key, iv, do, ciphername='aes-256-cbc'):
+ """
+ do == 1 => Encrypt; do == 0 => Decrypt
+ """
+ self.cipher = OpenSSL.get_cipher(ciphername)
+ self.ctx = OpenSSL.EVP_CIPHER_CTX_new()
+ if do == 1 or do == 0:
+ k = OpenSSL.malloc(key, len(key))
+ IV = OpenSSL.malloc(iv, len(iv))
+ OpenSSL.EVP_CipherInit_ex(
+ self.ctx, self.cipher.get_pointer(), 0, k, IV, do)
+ else:
+ raise Exception("RTFM ...")
+
+ @staticmethod
+ def get_all_cipher():
+ """
+ static method, returns all ciphers available
+ """
+ return OpenSSL.cipher_algo.keys()
+
+ @staticmethod
+ def get_blocksize(ciphername):
+ cipher = OpenSSL.get_cipher(ciphername)
+ return cipher.get_blocksize()
+
+ @staticmethod
+ def gen_IV(ciphername):
+ cipher = OpenSSL.get_cipher(ciphername)
+ return OpenSSL.rand(cipher.get_blocksize())
+
+ def update(self, input):
+ i = OpenSSL.c_int(0)
+ buffer = OpenSSL.malloc(b"", len(input) + self.cipher.get_blocksize())
+ inp = OpenSSL.malloc(input, len(input))
+ if OpenSSL.EVP_CipherUpdate(self.ctx, OpenSSL.byref(buffer),
+ OpenSSL.byref(i), inp, len(input)) == 0:
+ raise Exception("[OpenSSL] EVP_CipherUpdate FAIL ...")
+ return buffer.raw[0:i.value]
+
+ def final(self):
+ i = OpenSSL.c_int(0)
+ buffer = OpenSSL.malloc(b"", self.cipher.get_blocksize())
+ if (OpenSSL.EVP_CipherFinal_ex(self.ctx, OpenSSL.byref(buffer),
+ OpenSSL.byref(i))) == 0:
+ raise Exception("[OpenSSL] EVP_CipherFinal_ex FAIL ...")
+ return buffer.raw[0:i.value]
+
+ def ciphering(self, input):
+ """
+ Do update and final in one method
+ """
+ buff = self.update(input)
+ return buff + self.final()
+
+ def __del__(self):
+ OpenSSL.EVP_CIPHER_CTX_cleanup(self.ctx)
+ OpenSSL.EVP_CIPHER_CTX_free(self.ctx)
diff --git a/pyelliptic/ecc.py b/pyelliptic/ecc.py
new file mode 100644
index 00000000..2d1559b2
--- /dev/null
+++ b/pyelliptic/ecc.py
@@ -0,0 +1,460 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+# Copyright (C) 2011 Yann GUIBET
+# See LICENSE for details.
+
+from hashlib import sha512
+from pyelliptic.openssl import OpenSSL
+from pyelliptic.cipher import Cipher
+from pyelliptic.hash import hmac_sha256
+from struct import pack, unpack
+
+
+class ECC:
+ """
+ Asymmetric encryption with Elliptic Curve Cryptography (ECC)
+ ECDH, ECDSA and ECIES
+
+ import pyelliptic
+
+ alice = pyelliptic.ECC() # default curve: sect283r1
+ bob = pyelliptic.ECC(curve='sect571r1')
+
+ ciphertext = alice.encrypt("Hello Bob", bob.get_pubkey())
+ print bob.decrypt(ciphertext)
+
+ signature = bob.sign("Hello Alice")
+ # alice's job :
+ print pyelliptic.ECC(
+ pubkey=bob.get_pubkey()).verify(signature, "Hello Alice")
+
+ # ERROR !!!
+ try:
+ key = alice.get_ecdh_key(bob.get_pubkey())
+ except: print("For ECDH key agreement,\
+ the keys must be defined on the same curve !")
+
+ alice = pyelliptic.ECC(curve='sect571r1')
+ print alice.get_ecdh_key(bob.get_pubkey()).encode('hex')
+ print bob.get_ecdh_key(alice.get_pubkey()).encode('hex')
+
+ """
+ def __init__(self, pubkey=None, privkey=None, pubkey_x=None,
+ pubkey_y=None, raw_privkey=None, curve='sect283r1'):
+ """
+ For a normal and High level use, specifie pubkey,
+ privkey (if you need) and the curve
+ """
+ if type(curve) == str:
+ self.curve = OpenSSL.get_curve(curve)
+ else:
+ self.curve = curve
+
+ if pubkey_x is not None and pubkey_y is not None:
+ self._set_keys(pubkey_x, pubkey_y, raw_privkey)
+ elif pubkey is not None:
+ curve, pubkey_x, pubkey_y, i = ECC._decode_pubkey(pubkey)
+ if privkey is not None:
+ curve2, raw_privkey, i = ECC._decode_privkey(privkey)
+ if curve != curve2:
+ raise Exception("Bad ECC keys ...")
+ self.curve = curve
+ self._set_keys(pubkey_x, pubkey_y, raw_privkey)
+ else:
+ self.privkey, self.pubkey_x, self.pubkey_y = self._generate()
+
+ def _set_keys(self, pubkey_x, pubkey_y, privkey):
+ if self.raw_check_key(privkey, pubkey_x, pubkey_y) < 0:
+ self.pubkey_x = None
+ self.pubkey_y = None
+ self.privkey = None
+ raise Exception("Bad ECC keys ...")
+ else:
+ self.pubkey_x = pubkey_x
+ self.pubkey_y = pubkey_y
+ self.privkey = privkey
+
+ @staticmethod
+ def get_curves():
+ """
+ static method, returns the list of all the curves available
+ """
+ return OpenSSL.curves.keys()
+
+ def get_curve(self):
+ return OpenSSL.get_curve_by_id(self.curve)
+
+ def get_curve_id(self):
+ return self.curve
+
+ def get_pubkey(self):
+ """
+ High level function which returns :
+ curve(2) + len_of_pubkeyX(2) + pubkeyX + len_of_pubkeyY + pubkeyY
+ """
+ return b''.join((pack('!H', self.curve),
+ pack('!H', len(self.pubkey_x)),
+ self.pubkey_x,
+ pack('!H', len(self.pubkey_y)),
+ self.pubkey_y
+ ))
+
+ def get_privkey(self):
+ """
+ High level function which returns
+ curve(2) + len_of_privkey(2) + privkey
+ """
+ return b''.join((pack('!H', self.curve),
+ pack('!H', len(self.privkey)),
+ self.privkey
+ ))
+
+ @staticmethod
+ def _decode_pubkey(pubkey):
+ i = 0
+ curve = unpack('!H', pubkey[i:i + 2])[0]
+ i += 2
+ tmplen = unpack('!H', pubkey[i:i + 2])[0]
+ i += 2
+ pubkey_x = pubkey[i:i + tmplen]
+ i += tmplen
+ tmplen = unpack('!H', pubkey[i:i + 2])[0]
+ i += 2
+ pubkey_y = pubkey[i:i + tmplen]
+ i += tmplen
+ return curve, pubkey_x, pubkey_y, i
+
+ @staticmethod
+ def _decode_privkey(privkey):
+ i = 0
+ curve = unpack('!H', privkey[i:i + 2])[0]
+ i += 2
+ tmplen = unpack('!H', privkey[i:i + 2])[0]
+ i += 2
+ privkey = privkey[i:i + tmplen]
+ i += tmplen
+ return curve, privkey, i
+
+ def _generate(self):
+ try:
+ pub_key_x = OpenSSL.BN_new()
+ pub_key_y = OpenSSL.BN_new()
+
+ key = OpenSSL.EC_KEY_new_by_curve_name(self.curve)
+ if key == 0:
+ raise Exception("[OpenSSL] EC_KEY_new_by_curve_name FAIL ...")
+ if (OpenSSL.EC_KEY_generate_key(key)) == 0:
+ raise Exception("[OpenSSL] EC_KEY_generate_key FAIL ...")
+ if (OpenSSL.EC_KEY_check_key(key)) == 0:
+ raise Exception("[OpenSSL] EC_KEY_check_key FAIL ...")
+ priv_key = OpenSSL.EC_KEY_get0_private_key(key)
+
+ group = OpenSSL.EC_KEY_get0_group(key)
+ pub_key = OpenSSL.EC_KEY_get0_public_key(key)
+
+ if (OpenSSL.EC_POINT_get_affine_coordinates_GFp(group, pub_key,
+ pub_key_x,
+ pub_key_y, 0
+ )) == 0:
+ raise Exception(
+ "[OpenSSL] EC_POINT_get_affine_coordinates_GFp FAIL ...")
+
+ privkey = OpenSSL.malloc(0, OpenSSL.BN_num_bytes(priv_key))
+ pubkeyx = OpenSSL.malloc(0, OpenSSL.BN_num_bytes(pub_key_x))
+ pubkeyy = OpenSSL.malloc(0, OpenSSL.BN_num_bytes(pub_key_y))
+ OpenSSL.BN_bn2bin(priv_key, privkey)
+ privkey = privkey.raw
+ OpenSSL.BN_bn2bin(pub_key_x, pubkeyx)
+ pubkeyx = pubkeyx.raw
+ OpenSSL.BN_bn2bin(pub_key_y, pubkeyy)
+ pubkeyy = pubkeyy.raw
+ self.raw_check_key(privkey, pubkeyx, pubkeyy)
+
+ return privkey, pubkeyx, pubkeyy
+
+ finally:
+ OpenSSL.EC_KEY_free(key)
+ OpenSSL.BN_free(pub_key_x)
+ OpenSSL.BN_free(pub_key_y)
+
+ def get_ecdh_key(self, pubkey):
+ """
+ High level function. Compute public key with the local private key
+ and returns a 512bits shared key
+ """
+ curve, pubkey_x, pubkey_y, i = ECC._decode_pubkey(pubkey)
+ if curve != self.curve:
+ raise Exception("ECC keys must be from the same curve !")
+ return sha512(self.raw_get_ecdh_key(pubkey_x, pubkey_y)).digest()
+
+ def raw_get_ecdh_key(self, pubkey_x, pubkey_y):
+ try:
+ ecdh_keybuffer = OpenSSL.malloc(0, 32)
+
+ other_key = OpenSSL.EC_KEY_new_by_curve_name(self.curve)
+ if other_key == 0:
+ raise Exception("[OpenSSL] EC_KEY_new_by_curve_name FAIL ...")
+
+ other_pub_key_x = OpenSSL.BN_bin2bn(pubkey_x, len(pubkey_x), 0)
+ other_pub_key_y = OpenSSL.BN_bin2bn(pubkey_y, len(pubkey_y), 0)
+
+ other_group = OpenSSL.EC_KEY_get0_group(other_key)
+ other_pub_key = OpenSSL.EC_POINT_new(other_group)
+
+ if (OpenSSL.EC_POINT_set_affine_coordinates_GFp(other_group,
+ other_pub_key,
+ other_pub_key_x,
+ other_pub_key_y,
+ 0)) == 0:
+ raise Exception(
+ "[OpenSSL] EC_POINT_set_affine_coordinates_GFp FAIL ...")
+ if (OpenSSL.EC_KEY_set_public_key(other_key, other_pub_key)) == 0:
+ raise Exception("[OpenSSL] EC_KEY_set_public_key FAIL ...")
+ if (OpenSSL.EC_KEY_check_key(other_key)) == 0:
+ raise Exception("[OpenSSL] EC_KEY_check_key FAIL ...")
+
+ own_key = OpenSSL.EC_KEY_new_by_curve_name(self.curve)
+ if own_key == 0:
+ raise Exception("[OpenSSL] EC_KEY_new_by_curve_name FAIL ...")
+ own_priv_key = OpenSSL.BN_bin2bn(
+ self.privkey, len(self.privkey), 0)
+
+ if (OpenSSL.EC_KEY_set_private_key(own_key, own_priv_key)) == 0:
+ raise Exception("[OpenSSL] EC_KEY_set_private_key FAIL ...")
+
+ OpenSSL.ECDH_set_method(own_key, OpenSSL.ECDH_OpenSSL())
+ ecdh_keylen = OpenSSL.ECDH_compute_key(
+ ecdh_keybuffer, 32, other_pub_key, own_key, 0)
+
+ if ecdh_keylen != 32:
+ raise Exception("[OpenSSL] ECDH keylen FAIL ...")
+
+ return ecdh_keybuffer.raw
+
+ finally:
+ OpenSSL.EC_KEY_free(other_key)
+ OpenSSL.BN_free(other_pub_key_x)
+ OpenSSL.BN_free(other_pub_key_y)
+ OpenSSL.EC_POINT_free(other_pub_key)
+ OpenSSL.EC_KEY_free(own_key)
+ OpenSSL.BN_free(own_priv_key)
+
+ def check_key(self, privkey, pubkey):
+ """
+ Check the public key and the private key.
+ The private key is optional (replace by None)
+ """
+ curve, pubkey_x, pubkey_y, i = ECC._decode_pubkey(pubkey)
+ if privkey is None:
+ raw_privkey = None
+ curve2 = curve
+ else:
+ curve2, raw_privkey, i = ECC._decode_privkey(privkey)
+ if curve != curve2:
+ raise Exception("Bad public and private key")
+ return self.raw_check_key(raw_privkey, pubkey_x, pubkey_y, curve)
+
+ def raw_check_key(self, privkey, pubkey_x, pubkey_y, curve=None):
+ if curve is None:
+ curve = self.curve
+ elif type(curve) == str:
+ curve = OpenSSL.get_curve(curve)
+ else:
+ curve = curve
+ try:
+ key = OpenSSL.EC_KEY_new_by_curve_name(curve)
+ if key == 0:
+ raise Exception("[OpenSSL] EC_KEY_new_by_curve_name FAIL ...")
+ if privkey is not None:
+ priv_key = OpenSSL.BN_bin2bn(privkey, len(privkey), 0)
+ pub_key_x = OpenSSL.BN_bin2bn(pubkey_x, len(pubkey_x), 0)
+ pub_key_y = OpenSSL.BN_bin2bn(pubkey_y, len(pubkey_y), 0)
+
+ if privkey is not None:
+ if (OpenSSL.EC_KEY_set_private_key(key, priv_key)) == 0:
+ raise Exception(
+ "[OpenSSL] EC_KEY_set_private_key FAIL ...")
+
+ group = OpenSSL.EC_KEY_get0_group(key)
+ pub_key = OpenSSL.EC_POINT_new(group)
+
+ if (OpenSSL.EC_POINT_set_affine_coordinates_GFp(group, pub_key,
+ pub_key_x,
+ pub_key_y,
+ 0)) == 0:
+ raise Exception(
+ "[OpenSSL] EC_POINT_set_affine_coordinates_GFp FAIL ...")
+ if (OpenSSL.EC_KEY_set_public_key(key, pub_key)) == 0:
+ raise Exception("[OpenSSL] EC_KEY_set_public_key FAIL ...")
+ if (OpenSSL.EC_KEY_check_key(key)) == 0:
+ raise Exception("[OpenSSL] EC_KEY_check_key FAIL ...")
+ return 0
+
+ finally:
+ OpenSSL.EC_KEY_free(key)
+ OpenSSL.BN_free(pub_key_x)
+ OpenSSL.BN_free(pub_key_y)
+ OpenSSL.EC_POINT_free(pub_key)
+ if privkey is not None:
+ OpenSSL.BN_free(priv_key)
+
+ def sign(self, inputb):
+ """
+ Sign the input with ECDSA method and returns the signature
+ """
+ try:
+ size = len(inputb)
+ buff = OpenSSL.malloc(inputb, size)
+ digest = OpenSSL.malloc(0, 64)
+ md_ctx = OpenSSL.EVP_MD_CTX_create()
+ dgst_len = OpenSSL.pointer(OpenSSL.c_int(0))
+ siglen = OpenSSL.pointer(OpenSSL.c_int(0))
+ sig = OpenSSL.malloc(0, 151)
+
+ key = OpenSSL.EC_KEY_new_by_curve_name(self.curve)
+ if key == 0:
+ raise Exception("[OpenSSL] EC_KEY_new_by_curve_name FAIL ...")
+
+ priv_key = OpenSSL.BN_bin2bn(self.privkey, len(self.privkey), 0)
+ pub_key_x = OpenSSL.BN_bin2bn(self.pubkey_x, len(self.pubkey_x), 0)
+ pub_key_y = OpenSSL.BN_bin2bn(self.pubkey_y, len(self.pubkey_y), 0)
+
+ if (OpenSSL.EC_KEY_set_private_key(key, priv_key)) == 0:
+ raise Exception("[OpenSSL] EC_KEY_set_private_key FAIL ...")
+
+ group = OpenSSL.EC_KEY_get0_group(key)
+ pub_key = OpenSSL.EC_POINT_new(group)
+
+ if (OpenSSL.EC_POINT_set_affine_coordinates_GFp(group, pub_key,
+ pub_key_x,
+ pub_key_y,
+ 0)) == 0:
+ raise Exception(
+ "[OpenSSL] EC_POINT_set_affine_coordinates_GFp FAIL ...")
+ if (OpenSSL.EC_KEY_set_public_key(key, pub_key)) == 0:
+ raise Exception("[OpenSSL] EC_KEY_set_public_key FAIL ...")
+ if (OpenSSL.EC_KEY_check_key(key)) == 0:
+ raise Exception("[OpenSSL] EC_KEY_check_key FAIL ...")
+
+ OpenSSL.EVP_MD_CTX_init(md_ctx)
+ OpenSSL.EVP_DigestInit(md_ctx, OpenSSL.EVP_ecdsa())
+
+ if (OpenSSL.EVP_DigestUpdate(md_ctx, buff, size)) == 0:
+ raise Exception("[OpenSSL] EVP_DigestUpdate FAIL ...")
+ OpenSSL.EVP_DigestFinal(md_ctx, digest, dgst_len)
+ OpenSSL.ECDSA_sign(0, digest, dgst_len.contents, sig, siglen, key)
+ if (OpenSSL.ECDSA_verify(0, digest, dgst_len.contents, sig,
+ siglen.contents, key)) != 1:
+ raise Exception("[OpenSSL] ECDSA_verify FAIL ...")
+
+ return sig.raw[:siglen.contents.value]
+
+ finally:
+ OpenSSL.EC_KEY_free(key)
+ OpenSSL.BN_free(pub_key_x)
+ OpenSSL.BN_free(pub_key_y)
+ OpenSSL.BN_free(priv_key)
+ OpenSSL.EC_POINT_free(pub_key)
+ OpenSSL.EVP_MD_CTX_destroy(md_ctx)
+
+ def verify(self, sig, inputb):
+ """
+ Verify the signature with the input and the local public key.
+ Returns a boolean
+ """
+ try:
+ bsig = OpenSSL.malloc(sig, len(sig))
+ binputb = OpenSSL.malloc(inputb, len(inputb))
+ digest = OpenSSL.malloc(0, 64)
+ dgst_len = OpenSSL.pointer(OpenSSL.c_int(0))
+ md_ctx = OpenSSL.EVP_MD_CTX_create()
+
+ key = OpenSSL.EC_KEY_new_by_curve_name(self.curve)
+
+ if key == 0:
+ raise Exception("[OpenSSL] EC_KEY_new_by_curve_name FAIL ...")
+
+ pub_key_x = OpenSSL.BN_bin2bn(self.pubkey_x, len(self.pubkey_x), 0)
+ pub_key_y = OpenSSL.BN_bin2bn(self.pubkey_y, len(self.pubkey_y), 0)
+ group = OpenSSL.EC_KEY_get0_group(key)
+ pub_key = OpenSSL.EC_POINT_new(group)
+
+ if (OpenSSL.EC_POINT_set_affine_coordinates_GFp(group, pub_key,
+ pub_key_x,
+ pub_key_y,
+ 0)) == 0:
+ raise Exception(
+ "[OpenSSL] EC_POINT_set_affine_coordinates_GFp FAIL ...")
+ if (OpenSSL.EC_KEY_set_public_key(key, pub_key)) == 0:
+ raise Exception("[OpenSSL] EC_KEY_set_public_key FAIL ...")
+ if (OpenSSL.EC_KEY_check_key(key)) == 0:
+ raise Exception("[OpenSSL] EC_KEY_check_key FAIL ...")
+
+ OpenSSL.EVP_MD_CTX_init(md_ctx)
+ OpenSSL.EVP_DigestInit(md_ctx, OpenSSL.EVP_ecdsa())
+ if (OpenSSL.EVP_DigestUpdate(md_ctx, binputb, len(inputb))) == 0:
+ raise Exception("[OpenSSL] EVP_DigestUpdate FAIL ...")
+
+ OpenSSL.EVP_DigestFinal(md_ctx, digest, dgst_len)
+ ret = OpenSSL.ECDSA_verify(
+ 0, digest, dgst_len.contents, bsig, len(sig), key)
+
+ if ret == -1:
+ return False # Fail to Check
+ else:
+ if ret == 0:
+ return False # Bad signature !
+ else:
+ return True # Good
+ return False
+
+ finally:
+ OpenSSL.EC_KEY_free(key)
+ OpenSSL.BN_free(pub_key_x)
+ OpenSSL.BN_free(pub_key_y)
+ OpenSSL.EC_POINT_free(pub_key)
+ OpenSSL.EVP_MD_CTX_destroy(md_ctx)
+
+ @staticmethod
+ def encrypt(data, pubkey, ephemcurve=None, ciphername='aes-256-cbc'):
+ """
+ Encrypt data with ECIES method using the public key of the recipient.
+ """
+ curve, pubkey_x, pubkey_y, i = ECC._decode_pubkey(pubkey)
+ return ECC.raw_encrypt(data, pubkey_x, pubkey_y, curve=curve,
+ ephemcurve=ephemcurve, ciphername=ciphername)
+
+ @staticmethod
+ def raw_encrypt(data, pubkey_x, pubkey_y, curve='sect283r1',
+ ephemcurve=None, ciphername='aes-256-cbc'):
+ if ephemcurve is None:
+ ephemcurve = curve
+ ephem = ECC(curve=ephemcurve)
+ key = sha512(ephem.raw_get_ecdh_key(pubkey_x, pubkey_y)).digest()
+ key_e, key_m = key[:32], key[32:]
+ pubkey = ephem.get_pubkey()
+ iv = OpenSSL.rand(OpenSSL.get_cipher(ciphername).get_blocksize())
+ ctx = Cipher(key_e, iv, 1, ciphername)
+ ciphertext = ctx.ciphering(data)
+ mac = hmac_sha256(key_m, ciphertext)
+ return iv + pubkey + ciphertext + mac
+
+ def decrypt(self, data, ciphername='aes-256-cbc'):
+ """
+ Decrypt data with ECIES method using the local private key
+ """
+ blocksize = OpenSSL.get_cipher(ciphername).get_blocksize()
+ iv = data[:blocksize]
+ i = blocksize
+ curve, pubkey_x, pubkey_y, i2 = ECC._decode_pubkey(data[i:])
+ i += i2
+ ciphertext = data[i:len(data)-32]
+ i += len(ciphertext)
+ mac = data[i:]
+ key = sha512(self.raw_get_ecdh_key(pubkey_x, pubkey_y)).digest()
+ key_e, key_m = key[:32], key[32:]
+ if hmac_sha256(key_m, ciphertext) != mac:
+ raise RuntimeError("Fail to verify data")
+ ctx = Cipher(key_e, iv, 0, ciphername)
+ return ctx.ciphering(ciphertext)
diff --git a/pyelliptic/hash.py b/pyelliptic/hash.py
new file mode 100644
index 00000000..d8ade000
--- /dev/null
+++ b/pyelliptic/hash.py
@@ -0,0 +1,43 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+# Copyright (C) 2011 Yann GUIBET
+# See LICENSE for details.
+
+from pyelliptic.openssl import OpenSSL
+
+
+def hmac_sha256(k, m):
+ """
+ Compute the key and the message with HMAC SHA5256
+ """
+ key = OpenSSL.malloc(k, len(k))
+ d = OpenSSL.malloc(m, len(m))
+ md = OpenSSL.malloc(0, 32)
+ i = OpenSSL.pointer(OpenSSL.c_int(0))
+ OpenSSL.HMAC(OpenSSL.EVP_sha256(), key, len(k), d, len(m), md, i)
+ return md.raw
+
+
+def hmac_sha512(k, m):
+ """
+ Compute the key and the message with HMAC SHA512
+ """
+ key = OpenSSL.malloc(k, len(k))
+ d = OpenSSL.malloc(m, len(m))
+ md = OpenSSL.malloc(0, 64)
+ i = OpenSSL.pointer(OpenSSL.c_int(0))
+ OpenSSL.HMAC(OpenSSL.EVP_sha512(), key, len(k), d, len(m), md, i)
+ return md.raw
+
+
+def pbkdf2(password, salt=None, i=10000, keylen=64):
+ if salt is None:
+ salt = OpenSSL.rand(8)
+ p_password = OpenSSL.malloc(password, len(password))
+ p_salt = OpenSSL.malloc(salt, len(salt))
+ output = OpenSSL.malloc(0, keylen)
+ OpenSSL.PKCS5_PBKDF2_HMAC(p_password, len(password), p_salt,
+ len(p_salt), i, OpenSSL.EVP_sha256(),
+ keylen, output)
+ return salt, output.raw
diff --git a/pyelliptic/highlevelcrypto.py b/pyelliptic/highlevelcrypto.py
new file mode 100644
index 00000000..ec3dfa23
--- /dev/null
+++ b/pyelliptic/highlevelcrypto.py
@@ -0,0 +1,33 @@
+import pyelliptic
+import arithmetic as a
+def makeCryptor(privkey):
+ privkey_bin = '\x02\xca\x00 '+a.changebase(privkey,16,256,minlen=32)
+ pubkey = a.changebase(a.privtopub(privkey),16,256,minlen=65)[1:]
+ pubkey_bin = '\x02\xca\x00 '+pubkey[:32]+'\x00 '+pubkey[32:]
+ cryptor = pyelliptic.ECC(curve='secp256k1',privkey=privkey_bin,pubkey=pubkey_bin)
+ return cryptor
+def hexToPubkey(pubkey):
+ pubkey_raw = a.changebase(pubkey[2:],16,256,minlen=64)
+ pubkey_bin = '\x02\xca\x00 '+pubkey_raw[:32]+'\x00 '+pubkey_raw[32:]
+ return pubkey_bin
+def makePubCryptor(pubkey):
+ pubkey_bin = hexToPubkey(pubkey)
+ return pyelliptic.ECC(curve='secp256k1',pubkey=pubkey_bin)
+# Converts hex private key into hex public key
+def privToPub(privkey):
+ return a.privtopub(privkey)
+# Encrypts message with hex public key
+def encrypt(msg,hexPubkey):
+ return pyelliptic.ECC(curve='secp256k1').encrypt(msg,hexToPubkey(hexPubkey))
+# Decrypts message with hex private key
+def decrypt(msg,hexPrivkey):
+ return makeCryptor(hexPrivkey).decrypt(msg)
+# Decrypts message with an existing pyelliptic.ecc.ECC object
+def decryptFast(msg,cryptor):
+ return cryptor.decrypt(msg)
+# Signs with hex private key
+def sign(msg,hexPrivkey):
+ return makeCryptor(hexPrivkey).sign(msg)
+# Verifies with hex public key
+def verify(msg,sig,hexPubkey):
+ return makePubCryptor(hexPubkey).verify(sig,msg)
diff --git a/pyelliptic/openssl.py b/pyelliptic/openssl.py
new file mode 100644
index 00000000..a3a37ebe
--- /dev/null
+++ b/pyelliptic/openssl.py
@@ -0,0 +1,422 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+# Copyright (C) 2011 Yann GUIBET
+# See LICENSE for details.
+#
+# Software slightly changed by Jonathan Warren
+
+import sys
+import ctypes
+
+OpenSSL = None
+
+
+class CipherName:
+ def __init__(self, name, pointer, blocksize):
+ self._name = name
+ self._pointer = pointer
+ self._blocksize = blocksize
+
+ def __str__(self):
+ return "Cipher : " + self._name + " | Blocksize : " + str(self._blocksize) + " | Fonction pointer : " + str(self._pointer)
+
+ def get_pointer(self):
+ return self._pointer()
+
+ def get_name(self):
+ return self._name
+
+ def get_blocksize(self):
+ return self._blocksize
+
+
+class _OpenSSL:
+ """
+ Wrapper for OpenSSL using ctypes
+ """
+ def __init__(self, library):
+ """
+ Build the wrapper
+ """
+ self._lib = ctypes.CDLL(library)
+
+ self.pointer = ctypes.pointer
+ self.c_int = ctypes.c_int
+ self.byref = ctypes.byref
+ self.create_string_buffer = ctypes.create_string_buffer
+
+ self.BN_new = self._lib.BN_new
+ self.BN_new.restype = ctypes.c_void_p
+ self.BN_new.argtypes = []
+
+ self.BN_free = self._lib.BN_free
+ self.BN_free.restype = None
+ self.BN_free.argtypes = [ctypes.c_void_p]
+
+ self.BN_num_bits = self._lib.BN_num_bits
+ self.BN_num_bits.restype = ctypes.c_int
+ self.BN_num_bits.argtypes = [ctypes.c_void_p]
+
+ self.BN_bn2bin = self._lib.BN_bn2bin
+ self.BN_bn2bin.restype = ctypes.c_int
+ self.BN_bn2bin.argtypes = [ctypes.c_void_p, ctypes.c_void_p]
+
+ self.BN_bin2bn = self._lib.BN_bin2bn
+ self.BN_bin2bn.restype = ctypes.c_void_p
+ self.BN_bin2bn.argtypes = [ctypes.c_void_p, ctypes.c_int,
+ ctypes.c_void_p]
+
+ self.EC_KEY_free = self._lib.EC_KEY_free
+ self.EC_KEY_free.restype = None
+ self.EC_KEY_free.argtypes = [ctypes.c_void_p]
+
+ self.EC_KEY_new_by_curve_name = self._lib.EC_KEY_new_by_curve_name
+ self.EC_KEY_new_by_curve_name.restype = ctypes.c_void_p
+ self.EC_KEY_new_by_curve_name.argtypes = [ctypes.c_int]
+
+ self.EC_KEY_generate_key = self._lib.EC_KEY_generate_key
+ self.EC_KEY_generate_key.restype = ctypes.c_int
+ self.EC_KEY_generate_key.argtypes = [ctypes.c_void_p]
+
+ self.EC_KEY_check_key = self._lib.EC_KEY_check_key
+ self.EC_KEY_check_key.restype = ctypes.c_int
+ self.EC_KEY_check_key.argtypes = [ctypes.c_void_p]
+
+ self.EC_KEY_get0_private_key = self._lib.EC_KEY_get0_private_key
+ self.EC_KEY_get0_private_key.restype = ctypes.c_void_p
+ self.EC_KEY_get0_private_key.argtypes = [ctypes.c_void_p]
+
+ self.EC_KEY_get0_public_key = self._lib.EC_KEY_get0_public_key
+ self.EC_KEY_get0_public_key.restype = ctypes.c_void_p
+ self.EC_KEY_get0_public_key.argtypes = [ctypes.c_void_p]
+
+ self.EC_KEY_get0_group = self._lib.EC_KEY_get0_group
+ self.EC_KEY_get0_group.restype = ctypes.c_void_p
+ self.EC_KEY_get0_group.argtypes = [ctypes.c_void_p]
+
+ self.EC_POINT_get_affine_coordinates_GFp = self._lib.EC_POINT_get_affine_coordinates_GFp
+ self.EC_POINT_get_affine_coordinates_GFp.restype = ctypes.c_int
+ self.EC_POINT_get_affine_coordinates_GFp.argtypes = [ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p]
+
+ self.EC_KEY_set_private_key = self._lib.EC_KEY_set_private_key
+ self.EC_KEY_set_private_key.restype = ctypes.c_int
+ self.EC_KEY_set_private_key.argtypes = [ctypes.c_void_p,
+ ctypes.c_void_p]
+
+ self.EC_KEY_set_public_key = self._lib.EC_KEY_set_public_key
+ self.EC_KEY_set_public_key.restype = ctypes.c_int
+ self.EC_KEY_set_public_key.argtypes = [ctypes.c_void_p,
+ ctypes.c_void_p]
+
+ self.EC_KEY_set_group = self._lib.EC_KEY_set_group
+ self.EC_KEY_set_group.restype = ctypes.c_int
+ self.EC_KEY_set_group.argtypes = [ctypes.c_void_p, ctypes.c_void_p]
+
+ self.EC_POINT_set_affine_coordinates_GFp = self._lib.EC_POINT_set_affine_coordinates_GFp
+ self.EC_POINT_set_affine_coordinates_GFp.restype = ctypes.c_int
+ self.EC_POINT_set_affine_coordinates_GFp.argtypes = [ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p]
+
+ self.EC_POINT_new = self._lib.EC_POINT_new
+ self.EC_POINT_new.restype = ctypes.c_void_p
+ self.EC_POINT_new.argtypes = [ctypes.c_void_p]
+
+ self.EC_POINT_free = self._lib.EC_POINT_free
+ self.EC_POINT_free.restype = None
+ self.EC_POINT_free.argtypes = [ctypes.c_void_p]
+
+ self.BN_CTX_free = self._lib.BN_CTX_free
+ self.BN_CTX_free.restype = None
+ self.BN_CTX_free.argtypes = [ctypes.c_void_p]
+
+ self.EC_POINT_mul = self._lib.EC_POINT_mul
+ self.EC_POINT_mul.restype = None
+ self.EC_POINT_mul.argtypes = [ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p]
+
+ self.EC_KEY_set_private_key = self._lib.EC_KEY_set_private_key
+ self.EC_KEY_set_private_key.restype = ctypes.c_int
+ self.EC_KEY_set_private_key.argtypes = [ctypes.c_void_p,
+ ctypes.c_void_p]
+
+ self.ECDH_OpenSSL = self._lib.ECDH_OpenSSL
+ self._lib.ECDH_OpenSSL.restype = ctypes.c_void_p
+ self._lib.ECDH_OpenSSL.argtypes = []
+
+ self.BN_CTX_new = self._lib.BN_CTX_new
+ self._lib.BN_CTX_new.restype = ctypes.c_void_p
+ self._lib.BN_CTX_new.argtypes = []
+
+ self.ECDH_set_method = self._lib.ECDH_set_method
+ self._lib.ECDH_set_method.restype = ctypes.c_int
+ self._lib.ECDH_set_method.argtypes = [ctypes.c_void_p, ctypes.c_void_p]
+
+ self.ECDH_compute_key = self._lib.ECDH_compute_key
+ self.ECDH_compute_key.restype = ctypes.c_int
+ self.ECDH_compute_key.argtypes = [ctypes.c_void_p,
+ ctypes.c_int, ctypes.c_void_p, ctypes.c_void_p]
+
+ self.EVP_CipherInit_ex = self._lib.EVP_CipherInit_ex
+ self.EVP_CipherInit_ex.restype = ctypes.c_int
+ self.EVP_CipherInit_ex.argtypes = [ctypes.c_void_p,
+ ctypes.c_void_p, ctypes.c_void_p]
+
+ self.EVP_CIPHER_CTX_new = self._lib.EVP_CIPHER_CTX_new
+ self.EVP_CIPHER_CTX_new.restype = ctypes.c_void_p
+ self.EVP_CIPHER_CTX_new.argtypes = []
+
+ # Cipher
+ self.EVP_aes_128_cfb128 = self._lib.EVP_aes_128_cfb128
+ self.EVP_aes_128_cfb128.restype = ctypes.c_void_p
+ self.EVP_aes_128_cfb128.argtypes = []
+
+ self.EVP_aes_256_cfb128 = self._lib.EVP_aes_256_cfb128
+ self.EVP_aes_256_cfb128.restype = ctypes.c_void_p
+ self.EVP_aes_256_cfb128.argtypes = []
+
+ self.EVP_aes_128_cbc = self._lib.EVP_aes_128_cbc
+ self.EVP_aes_128_cbc.restype = ctypes.c_void_p
+ self.EVP_aes_128_cbc.argtypes = []
+
+ self.EVP_aes_256_cbc = self._lib.EVP_aes_256_cbc
+ self.EVP_aes_256_cbc.restype = ctypes.c_void_p
+ self.EVP_aes_256_cbc.argtypes = []
+
+ self.EVP_aes_128_ctr = self._lib.EVP_aes_128_ctr
+ self.EVP_aes_128_ctr.restype = ctypes.c_void_p
+ self.EVP_aes_128_ctr.argtypes = []
+
+ self.EVP_aes_256_ctr = self._lib.EVP_aes_256_ctr
+ self.EVP_aes_256_ctr.restype = ctypes.c_void_p
+ self.EVP_aes_256_ctr.argtypes = []
+
+ self.EVP_aes_128_ofb = self._lib.EVP_aes_128_ofb
+ self.EVP_aes_128_ofb.restype = ctypes.c_void_p
+ self.EVP_aes_128_ofb.argtypes = []
+
+ self.EVP_aes_256_ofb = self._lib.EVP_aes_256_ofb
+ self.EVP_aes_256_ofb.restype = ctypes.c_void_p
+ self.EVP_aes_256_ofb.argtypes = []
+
+ self.EVP_bf_cbc = self._lib.EVP_bf_cbc
+ self.EVP_bf_cbc.restype = ctypes.c_void_p
+ self.EVP_bf_cbc.argtypes = []
+
+ self.EVP_bf_cfb64 = self._lib.EVP_bf_cfb64
+ self.EVP_bf_cfb64.restype = ctypes.c_void_p
+ self.EVP_bf_cfb64.argtypes = []
+
+ self.EVP_rc4 = self._lib.EVP_rc4
+ self.EVP_rc4.restype = ctypes.c_void_p
+ self.EVP_rc4.argtypes = []
+
+ self.EVP_CIPHER_CTX_cleanup = self._lib.EVP_CIPHER_CTX_cleanup
+ self.EVP_CIPHER_CTX_cleanup.restype = ctypes.c_int
+ self.EVP_CIPHER_CTX_cleanup.argtypes = [ctypes.c_void_p]
+
+ self.EVP_CIPHER_CTX_free = self._lib.EVP_CIPHER_CTX_free
+ self.EVP_CIPHER_CTX_free.restype = None
+ self.EVP_CIPHER_CTX_free.argtypes = [ctypes.c_void_p]
+
+ self.EVP_CipherUpdate = self._lib.EVP_CipherUpdate
+ self.EVP_CipherUpdate.restype = ctypes.c_int
+ self.EVP_CipherUpdate.argtypes = [ctypes.c_void_p,
+ ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p, ctypes.c_int]
+
+ self.EVP_CipherFinal_ex = self._lib.EVP_CipherFinal_ex
+ self.EVP_CipherFinal_ex.restype = ctypes.c_int
+ self.EVP_CipherFinal_ex.argtypes = [ctypes.c_void_p,
+ ctypes.c_void_p, ctypes.c_void_p]
+
+ self.EVP_DigestInit = self._lib.EVP_DigestInit
+ self.EVP_DigestInit.restype = ctypes.c_int
+ self._lib.EVP_DigestInit.argtypes = [ctypes.c_void_p, ctypes.c_void_p]
+
+ self.EVP_DigestUpdate = self._lib.EVP_DigestUpdate
+ self.EVP_DigestUpdate.restype = ctypes.c_int
+ self.EVP_DigestUpdate.argtypes = [ctypes.c_void_p,
+ ctypes.c_void_p, ctypes.c_int]
+
+ self.EVP_DigestFinal = self._lib.EVP_DigestFinal
+ self.EVP_DigestFinal.restype = ctypes.c_int
+ self.EVP_DigestFinal.argtypes = [ctypes.c_void_p,
+ ctypes.c_void_p, ctypes.c_void_p]
+
+ self.EVP_ecdsa = self._lib.EVP_ecdsa
+ self._lib.EVP_ecdsa.restype = ctypes.c_void_p
+ self._lib.EVP_ecdsa.argtypes = []
+
+ self.ECDSA_sign = self._lib.ECDSA_sign
+ self.ECDSA_sign.restype = ctypes.c_int
+ self.ECDSA_sign.argtypes = [ctypes.c_int, ctypes.c_void_p,
+ ctypes.c_int, ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p]
+
+ self.ECDSA_verify = self._lib.ECDSA_verify
+ self.ECDSA_verify.restype = ctypes.c_int
+ self.ECDSA_verify.argtypes = [ctypes.c_int, ctypes.c_void_p,
+ ctypes.c_int, ctypes.c_void_p, ctypes.c_int, ctypes.c_void_p]
+
+ self.EVP_MD_CTX_create = self._lib.EVP_MD_CTX_create
+ self.EVP_MD_CTX_create.restype = ctypes.c_void_p
+ self.EVP_MD_CTX_create.argtypes = []
+
+ self.EVP_MD_CTX_init = self._lib.EVP_MD_CTX_init
+ self.EVP_MD_CTX_init.restype = None
+ self.EVP_MD_CTX_init.argtypes = [ctypes.c_void_p]
+
+ self.EVP_MD_CTX_destroy = self._lib.EVP_MD_CTX_destroy
+ self.EVP_MD_CTX_destroy.restype = None
+ self.EVP_MD_CTX_destroy.argtypes = [ctypes.c_void_p]
+
+ self.RAND_bytes = self._lib.RAND_bytes
+ self.RAND_bytes.restype = None
+ self.RAND_bytes.argtypes = [ctypes.c_void_p, ctypes.c_int]
+
+
+ self.EVP_sha256 = self._lib.EVP_sha256
+ self.EVP_sha256.restype = ctypes.c_void_p
+ self.EVP_sha256.argtypes = []
+
+ self.i2o_ECPublicKey = self._lib.i2o_ECPublicKey
+ self.i2o_ECPublicKey.restype = ctypes.c_void_p
+ self.i2o_ECPublicKey.argtypes = [ctypes.c_void_p, ctypes.c_void_p]
+
+ self.EVP_sha512 = self._lib.EVP_sha512
+ self.EVP_sha512.restype = ctypes.c_void_p
+ self.EVP_sha512.argtypes = []
+
+ self.HMAC = self._lib.HMAC
+ self.HMAC.restype = ctypes.c_void_p
+ self.HMAC.argtypes = [ctypes.c_void_p, ctypes.c_void_p, ctypes.c_int,
+ ctypes.c_void_p, ctypes.c_int, ctypes.c_void_p, ctypes.c_void_p]
+
+ self.PKCS5_PBKDF2_HMAC = self._lib.PKCS5_PBKDF2_HMAC
+ self.PKCS5_PBKDF2_HMAC.restype = ctypes.c_int
+ self.PKCS5_PBKDF2_HMAC.argtypes = [ctypes.c_void_p, ctypes.c_int,
+ ctypes.c_void_p, ctypes.c_int,
+ ctypes.c_int, ctypes.c_void_p,
+ ctypes.c_int, ctypes.c_void_p]
+
+ self._set_ciphers()
+ self._set_curves()
+
+ def _set_ciphers(self):
+ self.cipher_algo = {
+ 'aes-128-cbc': CipherName('aes-128-cbc', self.EVP_aes_128_cbc, 16),
+ 'aes-256-cbc': CipherName('aes-256-cbc', self.EVP_aes_256_cbc, 16),
+ 'aes-128-cfb': CipherName('aes-128-cfb', self.EVP_aes_128_cfb128, 16),
+ 'aes-256-cfb': CipherName('aes-256-cfb', self.EVP_aes_256_cfb128, 16),
+ 'aes-128-ofb': CipherName('aes-128-ofb', self._lib.EVP_aes_128_ofb, 16),
+ 'aes-256-ofb': CipherName('aes-256-ofb', self._lib.EVP_aes_256_ofb, 16),
+ 'aes-128-ctr': CipherName('aes-128-ctr', self._lib.EVP_aes_128_ctr, 16),
+ 'aes-256-ctr': CipherName('aes-256-ctr', self._lib.EVP_aes_256_ctr, 16),
+ 'bf-cfb': CipherName('bf-cfb', self.EVP_bf_cfb64, 8),
+ 'bf-cbc': CipherName('bf-cbc', self.EVP_bf_cbc, 8),
+ 'rc4': CipherName('rc4', self.EVP_rc4, 128), # 128 is the initialisation size not block size
+ }
+
+ def _set_curves(self):
+ self.curves = {
+ 'secp112r1': 704,
+ 'secp112r2': 705,
+ 'secp128r1': 706,
+ 'secp128r2': 707,
+ 'secp160k1': 708,
+ 'secp160r1': 709,
+ 'secp160r2': 710,
+ 'secp192k1': 711,
+ 'secp224k1': 712,
+ 'secp224r1': 713,
+ 'secp256k1': 714,
+ 'secp384r1': 715,
+ 'secp521r1': 716,
+ 'sect113r1': 717,
+ 'sect113r2': 718,
+ 'sect131r1': 719,
+ 'sect131r2': 720,
+ 'sect163k1': 721,
+ 'sect163r1': 722,
+ 'sect163r2': 723,
+ 'sect193r1': 724,
+ 'sect193r2': 725,
+ 'sect233k1': 726,
+ 'sect233r1': 727,
+ 'sect239k1': 728,
+ 'sect283k1': 729,
+ 'sect283r1': 730,
+ 'sect409k1': 731,
+ 'sect409r1': 732,
+ 'sect571k1': 733,
+ 'sect571r1': 734,
+ }
+
+ def BN_num_bytes(self, x):
+ """
+ returns the length of a BN (OpenSSl API)
+ """
+ return int((self.BN_num_bits(x) + 7) / 8)
+
+ def get_cipher(self, name):
+ """
+ returns the OpenSSL cipher instance
+ """
+ if name not in self.cipher_algo:
+ raise Exception("Unknown cipher")
+ return self.cipher_algo[name]
+
+ def get_curve(self, name):
+ """
+ returns the id of a elliptic curve
+ """
+ if name not in self.curves:
+ raise Exception("Unknown curve")
+ return self.curves[name]
+
+ def get_curve_by_id(self, id):
+ """
+ returns the name of a elliptic curve with his id
+ """
+ res = None
+ for i in self.curves:
+ if self.curves[i] == id:
+ res = i
+ break
+ if res is None:
+ raise Exception("Unknown curve")
+ return res
+
+ def rand(self, size):
+ """
+ OpenSSL random function
+ """
+ buffer = self.malloc(0, size)
+ self.RAND_bytes(buffer, size)
+ return buffer.raw
+
+ def malloc(self, data, size):
+ """
+ returns a create_string_buffer (ctypes)
+ """
+ buffer = None
+ if data != 0:
+ if sys.version_info.major == 3 and isinstance(data, type('')):
+ data = data.encode()
+ buffer = self.create_string_buffer(data, size)
+ else:
+ buffer = self.create_string_buffer(size)
+ return buffer
+
+try:
+ OpenSSL = _OpenSSL('libcrypto.so')
+except:
+ try:
+ OpenSSL = _OpenSSL('libeay32.dll')
+ except:
+ try:
+ OpenSSL = _OpenSSL('libcrypto.dylib')
+ except:
+ try:
+ from os import path
+ lib_path = path.join(sys._MEIPASS, "libeay32.dll")
+ OpenSSL = _OpenSSL(lib_path)
+ except:
+ raise Exception("Couldn't load the OpenSSL library. You must install it.")