some address generation work completed
This commit is contained in:
parent
f18eb5c91b
commit
f0666e6544
29
addresses.py
29
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-':
|
||||
|
|
|
@ -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'))
|
||||
|
|
|
@ -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", "<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>", 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))
|
||||
|
||||
|
|
|
@ -6,151 +6,301 @@
|
|||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>383</width>
|
||||
<height>258</height>
|
||||
<width>723</width>
|
||||
<height>704</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Create new Address</string>
|
||||
</property>
|
||||
<widget class="QDialogButtonBox" name="buttonBox">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>160</x>
|
||||
<y>220</y>
|
||||
<width>201</width>
|
||||
<height>32</height>
|
||||
</rect>
|
||||
<layout class="QFormLayout" name="formLayout">
|
||||
<property name="fieldGrowthPolicy">
|
||||
<enum>QFormLayout::AllNonFixedFieldsGrow</enum>
|
||||
</property>
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="standardButtons">
|
||||
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>10</x>
|
||||
<y>0</y>
|
||||
<width>361</width>
|
||||
<height>41</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Here you may generate as many addresses as you like. Indeed, creating and abandoning addresses is encouraged.</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignBottom|Qt::AlignLeading|Qt::AlignLeft</set>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>20</x>
|
||||
<y>50</y>
|
||||
<width>301</width>
|
||||
<height>20</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Label (not shown to anyone except you)</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QLineEdit" name="newaddresslabel">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>20</x>
|
||||
<y>70</y>
|
||||
<width>351</width>
|
||||
<height>20</height>
|
||||
</rect>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QRadioButton" name="radioButtonMostAvailable">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>20</x>
|
||||
<y>110</y>
|
||||
<width>401</width>
|
||||
<height>16</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Use the most available stream</string>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QRadioButton" name="radioButtonExisting">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>20</x>
|
||||
<y>150</y>
|
||||
<width>351</width>
|
||||
<height>18</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Use the same stream as an existing address</string>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QLabel" name="label_3">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>35</x>
|
||||
<y>127</y>
|
||||
<width>351</width>
|
||||
<height>20</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string> (best if this is the first of many addresses you will create)</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QLabel" name="label_4">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>37</x>
|
||||
<y>167</y>
|
||||
<width>351</width>
|
||||
<height>21</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>(saves you some bandwidth and processing power)</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QComboBox" name="comboBoxExisting">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>40</x>
|
||||
<y>190</y>
|
||||
<width>331</width>
|
||||
<height>22</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="editable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
<item row="0" column="0" colspan="2">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>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:</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignBottom|Qt::AlignLeading|Qt::AlignLeft</set>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0" colspan="2">
|
||||
<widget class="QLabel" name="label_5">
|
||||
<property name="text">
|
||||
<string><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></string>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="0" colspan="2">
|
||||
<widget class="Line" name="line">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>100</width>
|
||||
<height>2</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="0" colspan="2">
|
||||
<widget class="QRadioButton" name="radioButtonRandomAddress">
|
||||
<property name="text">
|
||||
<string>Use a random number generator to make an address</string>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="6" column="0">
|
||||
<widget class="QRadioButton" name="radioButtonDeterministicAddress">
|
||||
<property name="text">
|
||||
<string>Use a passpharase to make addresses</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="9" column="0" colspan="2">
|
||||
<widget class="QCheckBox" name="checkBoxEighteenByteRipe">
|
||||
<property name="text">
|
||||
<string>Spend several minutes of extra computing time to make the address(es) 1 or 2 characters shorter</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="8" column="0">
|
||||
<widget class="QGroupBox" name="groupBoxDeterministic">
|
||||
<property name="title">
|
||||
<string>Make deterministic addresses</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="0" column="0" colspan="4">
|
||||
<widget class="QLabel" name="label_6">
|
||||
<property name="text">
|
||||
<string>Passphrase</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0" colspan="7">
|
||||
<widget class="QLineEdit" name="lineEditPassphrase">
|
||||
<property name="inputMethodHints">
|
||||
<set>Qt::ImhHiddenText|Qt::ImhNoAutoUppercase|Qt::ImhNoPredictiveText</set>
|
||||
</property>
|
||||
<property name="echoMode">
|
||||
<enum>QLineEdit::Password</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0" colspan="4">
|
||||
<widget class="QLabel" name="label_7">
|
||||
<property name="text">
|
||||
<string>Retype passphrase</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0" colspan="7">
|
||||
<widget class="QLineEdit" name="lineEditPassphraseAgain">
|
||||
<property name="echoMode">
|
||||
<enum>QLineEdit::Password</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="0" colspan="7">
|
||||
<widget class="QLabel" name="label_8">
|
||||
<property name="text">
|
||||
<string>In addition to your passphrase, you must remember these numbers:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="0">
|
||||
<widget class="QLabel" name="label_9">
|
||||
<property name="text">
|
||||
<string>Address version number: 2</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="1">
|
||||
<spacer name="horizontalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>73</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item row="5" column="3">
|
||||
<widget class="QLabel" name="label_10">
|
||||
<property name="text">
|
||||
<string>Stream number: 1</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="4" colspan="2">
|
||||
<spacer name="horizontalSpacer_2">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>42</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item row="6" column="0" colspan="5">
|
||||
<widget class="QLabel" name="label_11">
|
||||
<property name="text">
|
||||
<string>Number of addresses to make based on your passphrase:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="6" column="5" colspan="2">
|
||||
<widget class="QSpinBox" name="spinBoxNumberOfAddressesToMake">
|
||||
<property name="minimum">
|
||||
<number>1</number>
|
||||
</property>
|
||||
<property name="value">
|
||||
<number>8</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="7" column="0">
|
||||
<widget class="QGroupBox" name="groupBox">
|
||||
<property name="title">
|
||||
<string>Randomly generate address</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_2">
|
||||
<item row="0" column="0" colspan="2">
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string>Label (not shown to anyone except you)</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0" colspan="2">
|
||||
<widget class="QLineEdit" name="newaddresslabel"/>
|
||||
</item>
|
||||
<item row="2" column="0" colspan="2">
|
||||
<widget class="QRadioButton" name="radioButtonMostAvailable">
|
||||
<property name="text">
|
||||
<string>Use the most available stream</string>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<widget class="QLabel" name="label_3">
|
||||
<property name="text">
|
||||
<string> (best if this is the first of many addresses you will create)</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="0" colspan="2">
|
||||
<widget class="QRadioButton" name="radioButtonExisting">
|
||||
<property name="text">
|
||||
<string>Use the same stream as an existing address</string>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="1">
|
||||
<widget class="QLabel" name="label_4">
|
||||
<property name="text">
|
||||
<string>(saves you some bandwidth and processing power)</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="6" column="0">
|
||||
<spacer name="horizontalSpacer_3">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>13</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item row="6" column="1">
|
||||
<widget class="QComboBox" name="comboBoxExisting">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="editable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
<zorder>comboBoxExisting</zorder>
|
||||
<zorder>label_3</zorder>
|
||||
<zorder>radioButtonExisting</zorder>
|
||||
<zorder>newaddresslabel</zorder>
|
||||
<zorder>label_4</zorder>
|
||||
<zorder>radioButtonMostAvailable</zorder>
|
||||
<zorder>label_2</zorder>
|
||||
<zorder>horizontalSpacer_3</zorder>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="10" column="0" colspan="2">
|
||||
<widget class="QDialogButtonBox" name="buttonBox">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="MinimumExpanding" vsizetype="MinimumExpanding">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>160</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="standardButtons">
|
||||
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections>
|
||||
|
@ -161,8 +311,8 @@
|
|||
<slot>accept()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>360</x>
|
||||
<y>234</y>
|
||||
<x>580</x>
|
||||
<y>644</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>157</x>
|
||||
|
@ -177,8 +327,8 @@
|
|||
<slot>reject()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>360</x>
|
||||
<y>240</y>
|
||||
<x>580</x>
|
||||
<y>650</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>286</x>
|
||||
|
@ -193,12 +343,44 @@
|
|||
<slot>setEnabled(bool)</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>30</x>
|
||||
<y>158</y>
|
||||
<x>60</x>
|
||||
<y>349</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>99</x>
|
||||
<y>199</y>
|
||||
<x>148</x>
|
||||
<y>394</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>radioButtonDeterministicAddress</sender>
|
||||
<signal>toggled(bool)</signal>
|
||||
<receiver>groupBoxDeterministic</receiver>
|
||||
<slot>setShown(bool)</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>92</x>
|
||||
<y>213</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>277</x>
|
||||
<y>601</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>radioButtonRandomAddress</sender>
|
||||
<signal>toggled(bool)</signal>
|
||||
<receiver>groupBox</receiver>
|
||||
<slot>setShown(bool)</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>72</x>
|
||||
<y>189</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>68</x>
|
||||
<y>268</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
|
|
17
pyelliptic/__init__.py
Normal file
17
pyelliptic/__init__.py
Normal file
|
@ -0,0 +1,17 @@
|
|||
# Copyright (C) 2010
|
||||
# Author: Yann GUIBET
|
||||
# Contact: <yannguibet@gmail.com>
|
||||
|
||||
__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
|
103
pyelliptic/arithmetic.py
Normal file
103
pyelliptic/arithmetic.py
Normal file
|
@ -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)))
|
81
pyelliptic/cipher.py
Normal file
81
pyelliptic/cipher.py
Normal file
|
@ -0,0 +1,81 @@
|
|||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright (C) 2011 Yann GUIBET <yannguibet@gmail.com>
|
||||
# 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)
|
460
pyelliptic/ecc.py
Normal file
460
pyelliptic/ecc.py
Normal file
|
@ -0,0 +1,460 @@
|
|||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright (C) 2011 Yann GUIBET <yannguibet@gmail.com>
|
||||
# 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)
|
43
pyelliptic/hash.py
Normal file
43
pyelliptic/hash.py
Normal file
|
@ -0,0 +1,43 @@
|
|||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright (C) 2011 Yann GUIBET <yannguibet@gmail.com>
|
||||
# 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
|
33
pyelliptic/highlevelcrypto.py
Normal file
33
pyelliptic/highlevelcrypto.py
Normal file
|
@ -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)
|
422
pyelliptic/openssl.py
Normal file
422
pyelliptic/openssl.py
Normal file
|
@ -0,0 +1,422 @@
|
|||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright (C) 2011 Yann GUIBET <yannguibet@gmail.com>
|
||||
# See LICENSE for details.
|
||||
#
|
||||
# Software slightly changed by Jonathan Warren <bitmessage at-symbol jonwarren.org>
|
||||
|
||||
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.")
|
Loading…
Reference in New Issue
Block a user