Attachments a (#13) sq n mg
* Formatted protocol and its docstrings * Fixed some docstrings in shared and state * Fixed google style docstrings in addresses * More docstrings and formatting fixes in highlevelcrypto and shutdown * .readthedocs.yml * When clicking a link in a message viewed in HTML mode, if the link represents a data blob, launch a "Save File" dialog and write the file directly, rather than opening the link in an external browser. * Add "Attach File" button to message composition. * pylint fixes.
This commit is contained in:
parent
bb40d970cd
commit
c4e5385554
9
.readthedocs.yml
Normal file
9
.readthedocs.yml
Normal file
|
@ -0,0 +1,9 @@
|
|||
version: 2
|
||||
|
||||
python:
|
||||
version: 2.7
|
||||
install:
|
||||
- requirements: docs/requirements.txt
|
||||
- method: setuptools
|
||||
path: .
|
||||
system_packages: true
|
|
@ -1,7 +1,5 @@
|
|||
"""
|
||||
src/addresses.py
|
||||
================
|
||||
|
||||
Operations with addresses
|
||||
"""
|
||||
# pylint: disable=redefined-outer-name,inconsistent-return-statements
|
||||
|
||||
|
@ -18,8 +16,9 @@ ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"
|
|||
def encodeBase58(num, alphabet=ALPHABET):
|
||||
"""Encode a number in Base X
|
||||
|
||||
`num`: The number to encode
|
||||
`alphabet`: The alphabet to use for encoding
|
||||
Args:
|
||||
num: The number to encode
|
||||
alphabet: The alphabet to use for encoding
|
||||
"""
|
||||
if num == 0:
|
||||
return alphabet[0]
|
||||
|
@ -27,7 +26,6 @@ def encodeBase58(num, alphabet=ALPHABET):
|
|||
base = len(alphabet)
|
||||
while num:
|
||||
rem = num % base
|
||||
# print 'num is:', num
|
||||
num = num // base
|
||||
arr.append(alphabet[rem])
|
||||
arr.reverse()
|
||||
|
@ -37,9 +35,9 @@ def encodeBase58(num, alphabet=ALPHABET):
|
|||
def decodeBase58(string, alphabet=ALPHABET):
|
||||
"""Decode a Base X encoded string into the number
|
||||
|
||||
Arguments:
|
||||
- `string`: The encoded string
|
||||
- `alphabet`: The alphabet to use for encoding
|
||||
Args:
|
||||
string: The encoded string
|
||||
alphabet: The alphabet to use for encoding
|
||||
"""
|
||||
base = len(alphabet)
|
||||
num = 0
|
||||
|
|
|
@ -11,6 +11,7 @@ import sys
|
|||
import textwrap
|
||||
import threading
|
||||
import time
|
||||
import base64
|
||||
from datetime import datetime, timedelta
|
||||
from sqlite3 import register_adapter
|
||||
|
||||
|
@ -161,6 +162,8 @@ class MyForm(settingsmixin.SMainWindow):
|
|||
"clicked()"), self.click_pushButtonTTL)
|
||||
QtCore.QObject.connect(self.ui.pushButtonClear, QtCore.SIGNAL(
|
||||
"clicked()"), self.click_pushButtonClear)
|
||||
QtCore.QObject.connect(self.ui.pushButtonAttach, QtCore.SIGNAL(
|
||||
"clicked()"), self.click_pushButtonAttach)
|
||||
QtCore.QObject.connect(self.ui.pushButtonSend, QtCore.SIGNAL(
|
||||
"clicked()"), self.click_pushButtonSend)
|
||||
QtCore.QObject.connect(self.ui.pushButtonFetchNamecoinID, QtCore.SIGNAL(
|
||||
|
@ -1951,6 +1954,23 @@ class MyForm(settingsmixin.SMainWindow):
|
|||
self.ui.textEditMessage.reset()
|
||||
self.ui.comboBoxSendFrom.setCurrentIndex(0)
|
||||
|
||||
def click_pushButtonAttach(self):
|
||||
"""Launch a file picker and append to the current message the base64-encoded contents of the chosen file."""
|
||||
filename = QtGui.QFileDialog.getOpenFileName(self, "Attach File")
|
||||
if filename:
|
||||
f = open(filename, 'rb')
|
||||
data = f.read()
|
||||
f.close()
|
||||
data_b64 = base64.b64encode(data)
|
||||
html_data = '<a href="data:application/octet-stream;base64,' + data_b64 + '">' \
|
||||
+ os.path.basename(unicode(filename)) + '</a>'
|
||||
if self.ui.tabWidgetSend.currentIndex() == self.ui.tabWidgetSend.indexOf(self.ui.sendDirect):
|
||||
# send direct message
|
||||
self.ui.textEditMessage.insertPlainText(html_data)
|
||||
else:
|
||||
# send broadcast message
|
||||
self.ui.textEditMessageBroadcast.insertPlainText(html_data)
|
||||
|
||||
def click_pushButtonSend(self):
|
||||
encoding = 3 if QtGui.QApplication.queryKeyboardModifiers() & QtCore.Qt.ShiftModifier else 2
|
||||
|
||||
|
|
|
@ -340,6 +340,9 @@ class Ui_MainWindow(object):
|
|||
self.pushButtonClear = QtGui.QPushButton(self.send)
|
||||
self.pushButtonClear.setObjectName(_fromUtf8("pushButtonClear"))
|
||||
self.horizontalLayout_5.addWidget(self.pushButtonClear, 0, QtCore.Qt.AlignRight)
|
||||
self.pushButtonAttach = QtGui.QPushButton(self.send)
|
||||
self.pushButtonAttach.setObjectName(_fromUtf8("pushButtonAttach"))
|
||||
self.horizontalLayout_5.addWidget(self.pushButtonAttach, 0, QtCore.Qt.AlignRight)
|
||||
self.pushButtonSend = QtGui.QPushButton(self.send)
|
||||
self.pushButtonSend.setObjectName(_fromUtf8("pushButtonSend"))
|
||||
self.horizontalLayout_5.addWidget(self.pushButtonSend, 0, QtCore.Qt.AlignRight)
|
||||
|
@ -713,6 +716,7 @@ class Ui_MainWindow(object):
|
|||
pass
|
||||
self.labelHumanFriendlyTTLDescription.setText(_translate("MainWindow", "%n hour(s)", None, QtCore.QCoreApplication.CodecForTr, hours))
|
||||
self.pushButtonClear.setText(_translate("MainWindow", "Clear", None))
|
||||
self.pushButtonAttach.setText(_translate("MainWindow", "Attach File", None))
|
||||
self.pushButtonSend.setText(_translate("MainWindow", "Send", None))
|
||||
self.tabWidget.setTabText(self.tabWidget.indexOf(self.send), _translate("MainWindow", "Send", None))
|
||||
self.treeWidgetSubscriptions.headerItem().setText(0, _translate("MainWindow", "Subscriptions", None))
|
||||
|
|
|
@ -594,6 +594,19 @@ p, li { white-space: pre-wrap; }
|
|||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="pushButtonAttach">
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>16777215</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Attach File</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
|
|
|
@ -5,6 +5,8 @@ src/bitmessageqt/messageview.py
|
|||
"""
|
||||
|
||||
from PyQt4 import QtCore, QtGui
|
||||
import re
|
||||
import base64
|
||||
|
||||
from safehtmlparser import SafeHTMLParser
|
||||
|
||||
|
@ -64,6 +66,9 @@ class MessageView(QtGui.QTextBrowser):
|
|||
|
||||
def confirmURL(self, link):
|
||||
"""Show a dialog requesting URL opening confirmation"""
|
||||
link_str = link.toString()
|
||||
datablob_re = r'^data:.*/.*;base64,.*'
|
||||
datablob_match = re.match(datablob_re, link_str)
|
||||
if link.scheme() == "mailto":
|
||||
window = QtGui.QApplication.activeWindow()
|
||||
window.ui.lineEditTo.setText(link.path())
|
||||
|
@ -80,19 +85,29 @@ class MessageView(QtGui.QTextBrowser):
|
|||
)
|
||||
window.ui.textEditMessage.setFocus()
|
||||
return
|
||||
reply = QtGui.QMessageBox.warning(
|
||||
self,
|
||||
QtGui.QApplication.translate(
|
||||
"MessageView",
|
||||
"Follow external link"),
|
||||
QtGui.QApplication.translate(
|
||||
"MessageView",
|
||||
"The link \"%1\" will open in a browser. It may be a security risk, it could de-anonymise you"
|
||||
" or download malicious data. Are you sure?").arg(unicode(link.toString())),
|
||||
QtGui.QMessageBox.Yes,
|
||||
QtGui.QMessageBox.No)
|
||||
if reply == QtGui.QMessageBox.Yes:
|
||||
QtGui.QDesktopServices.openUrl(link)
|
||||
if datablob_match:
|
||||
name = QtGui.QFileDialog.getSaveFileName(self, 'Save File')
|
||||
if name:
|
||||
f = open(name, 'wb')
|
||||
data_begin_pos = re.finditer(";base64,", link_str).next()
|
||||
data_b64 = link_str[data_begin_pos.span()[1]:]
|
||||
data = base64.b64decode(data_b64)
|
||||
f.write(data)
|
||||
f.close()
|
||||
else:
|
||||
reply = QtGui.QMessageBox.warning(
|
||||
self,
|
||||
QtGui.QApplication.translate(
|
||||
"MessageView",
|
||||
"Follow external link"),
|
||||
QtGui.QApplication.translate(
|
||||
"MessageView",
|
||||
"The link \"%1\" will open in a browser. It may be a security risk, it could de-anonymise you"
|
||||
" or download malicious data. Are you sure?").arg(unicode(link.toString())),
|
||||
QtGui.QMessageBox.Yes,
|
||||
QtGui.QMessageBox.No)
|
||||
if reply == QtGui.QMessageBox.Yes:
|
||||
QtGui.QDesktopServices.openUrl(link)
|
||||
|
||||
def loadResource(self, restype, name):
|
||||
"""
|
||||
|
|
|
@ -1,6 +1,10 @@
|
|||
"""
|
||||
src/highlevelcrypto.py
|
||||
======================
|
||||
High level cryptographic functions based on `.pyelliptic` OpenSSL bindings.
|
||||
|
||||
.. note::
|
||||
Upstream pyelliptic was upgraded from SHA1 to SHA256 for signing.
|
||||
We must upgrade PyBitmessage gracefully.
|
||||
`More discussion. <https://github.com/yann2192/pyelliptic/issues/32>`_
|
||||
"""
|
||||
|
||||
from binascii import hexlify
|
||||
|
@ -12,12 +16,13 @@ from pyelliptic import arithmetic as a
|
|||
|
||||
|
||||
def makeCryptor(privkey):
|
||||
"""Return a private pyelliptic.ECC() instance"""
|
||||
"""Return a private `.pyelliptic.ECC` instance"""
|
||||
private_key = a.changebase(privkey, 16, 256, minlen=32)
|
||||
public_key = pointMult(private_key)
|
||||
privkey_bin = '\x02\xca\x00\x20' + private_key
|
||||
pubkey_bin = '\x02\xca\x00\x20' + public_key[1:-32] + '\x00\x20' + public_key[-32:]
|
||||
cryptor = pyelliptic.ECC(curve='secp256k1', privkey=privkey_bin, pubkey=pubkey_bin)
|
||||
cryptor = pyelliptic.ECC(
|
||||
curve='secp256k1', privkey=privkey_bin, pubkey=pubkey_bin)
|
||||
return cryptor
|
||||
|
||||
|
||||
|
@ -29,7 +34,7 @@ def hexToPubkey(pubkey):
|
|||
|
||||
|
||||
def makePubCryptor(pubkey):
|
||||
"""Return a public pyelliptic.ECC() instance"""
|
||||
"""Return a public `.pyelliptic.ECC` instance"""
|
||||
pubkey_bin = hexToPubkey(pubkey)
|
||||
return pyelliptic.ECC(curve='secp256k1', pubkey=pubkey_bin)
|
||||
|
||||
|
@ -43,7 +48,8 @@ def privToPub(privkey):
|
|||
|
||||
def encrypt(msg, hexPubkey):
|
||||
"""Encrypts message with hex public key"""
|
||||
return pyelliptic.ECC(curve='secp256k1').encrypt(msg, hexToPubkey(hexPubkey))
|
||||
return pyelliptic.ECC(curve='secp256k1').encrypt(
|
||||
msg, hexToPubkey(hexPubkey))
|
||||
|
||||
|
||||
def decrypt(msg, hexPrivkey):
|
||||
|
@ -52,36 +58,38 @@ def decrypt(msg, hexPrivkey):
|
|||
|
||||
|
||||
def decryptFast(msg, cryptor):
|
||||
"""Decrypts message with an existing pyelliptic.ECC.ECC object"""
|
||||
"""Decrypts message with an existing `.pyelliptic.ECC` object"""
|
||||
return cryptor.decrypt(msg)
|
||||
|
||||
|
||||
def sign(msg, hexPrivkey):
|
||||
"""Signs with hex private key"""
|
||||
# pyelliptic is upgrading from SHA1 to SHA256 for signing. We must
|
||||
# upgrade PyBitmessage gracefully.
|
||||
# https://github.com/yann2192/pyelliptic/pull/33
|
||||
# More discussion: https://github.com/yann2192/pyelliptic/issues/32
|
||||
digestAlg = BMConfigParser().safeGet('bitmessagesettings', 'digestalg', 'sha1')
|
||||
"""
|
||||
Signs with hex private key using SHA1 or SHA256 depending on
|
||||
"digestalg" setting
|
||||
"""
|
||||
digestAlg = BMConfigParser().safeGet(
|
||||
'bitmessagesettings', 'digestalg', 'sha1')
|
||||
if digestAlg == "sha1":
|
||||
# SHA1, this will eventually be deprecated
|
||||
return makeCryptor(hexPrivkey).sign(msg, digest_alg=OpenSSL.digest_ecdsa_sha1)
|
||||
return makeCryptor(hexPrivkey).sign(
|
||||
msg, digest_alg=OpenSSL.digest_ecdsa_sha1)
|
||||
elif digestAlg == "sha256":
|
||||
# SHA256. Eventually this will become the default
|
||||
return makeCryptor(hexPrivkey).sign(msg, digest_alg=OpenSSL.EVP_sha256)
|
||||
else:
|
||||
raise ValueError("Unknown digest algorithm %s" % (digestAlg))
|
||||
raise ValueError("Unknown digest algorithm %s" % digestAlg)
|
||||
|
||||
|
||||
def verify(msg, sig, hexPubkey):
|
||||
"""Verifies with hex public key"""
|
||||
"""Verifies with hex public key using SHA1 or SHA256"""
|
||||
# As mentioned above, we must upgrade gracefully to use SHA256. So
|
||||
# let us check the signature using both SHA1 and SHA256 and if one
|
||||
# of them passes then we will be satisfied. Eventually this can
|
||||
# be simplified and we'll only check with SHA256.
|
||||
try:
|
||||
# old SHA1 algorithm.
|
||||
sigVerifyPassed = makePubCryptor(hexPubkey).verify(sig, msg, digest_alg=OpenSSL.digest_ecdsa_sha1)
|
||||
sigVerifyPassed = makePubCryptor(hexPubkey).verify(
|
||||
sig, msg, digest_alg=OpenSSL.digest_ecdsa_sha1)
|
||||
except:
|
||||
sigVerifyPassed = False
|
||||
if sigVerifyPassed:
|
||||
|
@ -89,7 +97,8 @@ def verify(msg, sig, hexPubkey):
|
|||
return True
|
||||
# The signature check using SHA1 failed. Let us try it with SHA256.
|
||||
try:
|
||||
return makePubCryptor(hexPubkey).verify(sig, msg, digest_alg=OpenSSL.EVP_sha256)
|
||||
return makePubCryptor(hexPubkey).verify(
|
||||
sig, msg, digest_alg=OpenSSL.EVP_sha256)
|
||||
except:
|
||||
return False
|
||||
|
||||
|
@ -106,7 +115,8 @@ def pointMult(secret):
|
|||
"""
|
||||
while True:
|
||||
try:
|
||||
k = OpenSSL.EC_KEY_new_by_curve_name(OpenSSL.get_curve('secp256k1'))
|
||||
k = OpenSSL.EC_KEY_new_by_curve_name(
|
||||
OpenSSL.get_curve('secp256k1'))
|
||||
priv_key = OpenSSL.BN_bin2bn(secret, 32, None)
|
||||
group = OpenSSL.EC_KEY_get0_group(k)
|
||||
pub_key = OpenSSL.EC_POINT_new(group)
|
||||
|
|
139
src/protocol.py
139
src/protocol.py
|
@ -1,7 +1,8 @@
|
|||
# pylint: disable=too-many-boolean-expressions,too-many-return-statements,too-many-locals,too-many-statements
|
||||
"""
|
||||
Low-level protocol-related functions.
|
||||
"""
|
||||
# pylint: disable=too-many-boolean-expressions,too-many-return-statements
|
||||
# pylint: disable=too-many-locals,too-many-statements
|
||||
|
||||
import base64
|
||||
import hashlib
|
||||
|
@ -9,7 +10,6 @@ import random
|
|||
import socket
|
||||
import sys
|
||||
import time
|
||||
import traceback
|
||||
from binascii import hexlify
|
||||
from struct import pack, unpack, Struct
|
||||
|
||||
|
@ -24,10 +24,18 @@ from fallback import RIPEMD160Hash
|
|||
from helper_sql import sqlExecute
|
||||
from version import softwareVersion
|
||||
|
||||
|
||||
# Service flags
|
||||
#: This is a normal network node
|
||||
NODE_NETWORK = 1
|
||||
#: This node supports SSL/TLS in the current connect (python < 2.7.9
|
||||
#: only supports an SSL client, so in that case it would only have this
|
||||
#: on when the connection is a client).
|
||||
NODE_SSL = 2
|
||||
# (Proposal) This node may do PoW on behalf of some its peers
|
||||
# (PoW offloading/delegating), but it doesn't have to. Clients may have
|
||||
# to meet additional requirements (e.g. TLS authentication)
|
||||
# NODE_POW = 4
|
||||
#: Node supports dandelion
|
||||
NODE_DANDELION = 8
|
||||
|
||||
# Bitfield flags
|
||||
|
@ -89,7 +97,8 @@ def isBitSetWithinBitfield(fourByteString, n):
|
|||
def encodeHost(host):
|
||||
"""Encode a given host to be used in low-level socket operations"""
|
||||
if host.find('.onion') > -1:
|
||||
return '\xfd\x87\xd8\x7e\xeb\x43' + base64.b32decode(host.split(".")[0], True)
|
||||
return '\xfd\x87\xd8\x7e\xeb\x43' + base64.b32decode(
|
||||
host.split(".")[0], True)
|
||||
elif host.find(':') == -1:
|
||||
return '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xFF\xFF' + \
|
||||
socket.inet_aton(host)
|
||||
|
@ -134,7 +143,10 @@ def network_group(host):
|
|||
|
||||
|
||||
def checkIPAddress(host, private=False):
|
||||
"""Returns hostStandardFormat if it is a valid IP address, otherwise returns False"""
|
||||
"""
|
||||
Returns hostStandardFormat if it is a valid IP address,
|
||||
otherwise returns False
|
||||
"""
|
||||
if host[0:12] == '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xFF\xFF':
|
||||
hostStandardFormat = socket.inet_ntop(socket.AF_INET, host[12:])
|
||||
return checkIPv4Address(host[12:], hostStandardFormat, private)
|
||||
|
@ -150,35 +162,46 @@ def checkIPAddress(host, private=False):
|
|||
except ValueError:
|
||||
return False
|
||||
if hostStandardFormat == "":
|
||||
# This can happen on Windows systems which are not 64-bit compatible
|
||||
# so let us drop the IPv6 address.
|
||||
# This can happen on Windows systems which are
|
||||
# not 64-bit compatible so let us drop the IPv6 address.
|
||||
return False
|
||||
return checkIPv6Address(host, hostStandardFormat, private)
|
||||
|
||||
|
||||
def checkIPv4Address(host, hostStandardFormat, private=False):
|
||||
"""Returns hostStandardFormat if it is an IPv4 address, otherwise returns False"""
|
||||
"""
|
||||
Returns hostStandardFormat if it is an IPv4 address,
|
||||
otherwise returns False
|
||||
"""
|
||||
if host[0] == '\x7F': # 127/8
|
||||
if not private:
|
||||
logger.debug('Ignoring IP address in loopback range: %s', hostStandardFormat)
|
||||
logger.debug(
|
||||
'Ignoring IP address in loopback range: %s',
|
||||
hostStandardFormat)
|
||||
return hostStandardFormat if private else False
|
||||
if host[0] == '\x0A': # 10/8
|
||||
if not private:
|
||||
logger.debug('Ignoring IP address in private range: %s', hostStandardFormat)
|
||||
logger.debug(
|
||||
'Ignoring IP address in private range: %s', hostStandardFormat)
|
||||
return hostStandardFormat if private else False
|
||||
if host[0:2] == '\xC0\xA8': # 192.168/16
|
||||
if not private:
|
||||
logger.debug('Ignoring IP address in private range: %s', hostStandardFormat)
|
||||
logger.debug(
|
||||
'Ignoring IP address in private range: %s', hostStandardFormat)
|
||||
return hostStandardFormat if private else False
|
||||
if host[0:2] >= '\xAC\x10' and host[0:2] < '\xAC\x20': # 172.16/12
|
||||
if not private:
|
||||
logger.debug('Ignoring IP address in private range: %s', hostStandardFormat)
|
||||
logger.debug(
|
||||
'Ignoring IP address in private range: %s', hostStandardFormat)
|
||||
return hostStandardFormat if private else False
|
||||
return False if private else hostStandardFormat
|
||||
|
||||
|
||||
def checkIPv6Address(host, hostStandardFormat, private=False):
|
||||
"""Returns hostStandardFormat if it is an IPv6 address, otherwise returns False"""
|
||||
"""
|
||||
Returns hostStandardFormat if it is an IPv6 address,
|
||||
otherwise returns False
|
||||
"""
|
||||
if host == ('\x00' * 15) + '\x01':
|
||||
if not private:
|
||||
logger.debug('Ignoring loopback address: %s', hostStandardFormat)
|
||||
|
@ -189,7 +212,8 @@ def checkIPv6Address(host, hostStandardFormat, private=False):
|
|||
return hostStandardFormat if private else False
|
||||
if (ord(host[0]) & 0xfe) == 0xfc:
|
||||
if not private:
|
||||
logger.debug('Ignoring unique local address: %s', hostStandardFormat)
|
||||
logger.debug(
|
||||
'Ignoring unique local address: %s', hostStandardFormat)
|
||||
return hostStandardFormat if private else False
|
||||
return False if private else hostStandardFormat
|
||||
|
||||
|
@ -210,31 +234,29 @@ def haveSSL(server=False):
|
|||
|
||||
def checkSocksIP(host):
|
||||
"""Predicate to check if we're using a SOCKS proxy"""
|
||||
sockshostname = BMConfigParser().safeGet(
|
||||
'bitmessagesettings', 'sockshostname')
|
||||
try:
|
||||
if state.socksIP is None or not state.socksIP:
|
||||
state.socksIP = socket.gethostbyname(BMConfigParser().get("bitmessagesettings", "sockshostname"))
|
||||
# uninitialised
|
||||
except NameError:
|
||||
state.socksIP = socket.gethostbyname(BMConfigParser().get("bitmessagesettings", "sockshostname"))
|
||||
# resolving failure
|
||||
except socket.gaierror:
|
||||
state.socksIP = BMConfigParser().get("bitmessagesettings", "sockshostname")
|
||||
if not state.socksIP:
|
||||
state.socksIP = socket.gethostbyname(sockshostname)
|
||||
except NameError: # uninitialised
|
||||
state.socksIP = socket.gethostbyname(sockshostname)
|
||||
except (TypeError, socket.gaierror): # None, resolving failure
|
||||
state.socksIP = sockshostname
|
||||
return state.socksIP == host
|
||||
|
||||
|
||||
def isProofOfWorkSufficient(data,
|
||||
nonceTrialsPerByte=0,
|
||||
payloadLengthExtraBytes=0,
|
||||
recvTime=0):
|
||||
def isProofOfWorkSufficient(
|
||||
data, nonceTrialsPerByte=0, payloadLengthExtraBytes=0, recvTime=0):
|
||||
"""
|
||||
Validate an object's Proof of Work using method described in:
|
||||
https://bitmessage.org/wiki/Proof_of_work
|
||||
Validate an object's Proof of Work using method described
|
||||
`here <https://bitmessage.org/wiki/Proof_of_work>`_
|
||||
|
||||
Arguments:
|
||||
int nonceTrialsPerByte (default: from default.py)
|
||||
int payloadLengthExtraBytes (default: from default.py)
|
||||
int nonceTrialsPerByte (default: from `.defaults`)
|
||||
int payloadLengthExtraBytes (default: from `.defaults`)
|
||||
float recvTime (optional) UNIX epoch time when object was
|
||||
received from the network (default: current system time)
|
||||
received from the network (default: current system time)
|
||||
Returns:
|
||||
True if PoW valid and sufficient, False in all other cases
|
||||
"""
|
||||
|
@ -246,18 +268,20 @@ def isProofOfWorkSufficient(data,
|
|||
TTL = endOfLifeTime - (int(recvTime) if recvTime else int(time.time()))
|
||||
if TTL < 300:
|
||||
TTL = 300
|
||||
POW, = unpack('>Q', hashlib.sha512(hashlib.sha512(data[
|
||||
:8] + hashlib.sha512(data[8:]).digest()).digest()).digest()[0:8])
|
||||
return POW <= 2 ** 64 / (nonceTrialsPerByte *
|
||||
(len(data) + payloadLengthExtraBytes +
|
||||
((TTL * (len(data) + payloadLengthExtraBytes)) / (2 ** 16))))
|
||||
POW, = unpack('>Q', hashlib.sha512(hashlib.sha512(
|
||||
data[:8] + hashlib.sha512(data[8:]).digest()
|
||||
).digest()).digest()[0:8])
|
||||
return POW <= 2 ** 64 / (
|
||||
nonceTrialsPerByte * (
|
||||
len(data) + payloadLengthExtraBytes +
|
||||
((TTL * (len(data) + payloadLengthExtraBytes)) / (2 ** 16))))
|
||||
|
||||
|
||||
# Packet creation
|
||||
|
||||
|
||||
def CreatePacket(command, payload=''):
|
||||
"""Construct and return a number of bytes from a payload"""
|
||||
"""Construct and return a packet"""
|
||||
payload_length = len(payload)
|
||||
checksum = hashlib.sha512(payload).digest()[0:4]
|
||||
|
||||
|
@ -267,8 +291,13 @@ def CreatePacket(command, payload=''):
|
|||
return bytes(b)
|
||||
|
||||
|
||||
def assembleVersionMessage(remoteHost, remotePort, participatingStreams, server=False, nodeid=None):
|
||||
"""Construct the payload of a version message, return the resultng bytes of running CreatePacket() on it"""
|
||||
def assembleVersionMessage(
|
||||
remoteHost, remotePort, participatingStreams, server=False, nodeid=None
|
||||
):
|
||||
"""
|
||||
Construct the payload of a version message,
|
||||
return the resulting bytes of running `CreatePacket` on it
|
||||
"""
|
||||
payload = ''
|
||||
payload += pack('>L', 3) # protocol version.
|
||||
# bitflags of the services I offer.
|
||||
|
@ -280,9 +309,10 @@ def assembleVersionMessage(remoteHost, remotePort, participatingStreams, server=
|
|||
)
|
||||
payload += pack('>q', int(time.time()))
|
||||
|
||||
payload += pack(
|
||||
'>q', 1) # boolservices of remote connection; ignored by the remote host.
|
||||
if checkSocksIP(remoteHost) and server: # prevent leaking of tor outbound IP
|
||||
# boolservices of remote connection; ignored by the remote host.
|
||||
payload += pack('>q', 1)
|
||||
if checkSocksIP(remoteHost) and server:
|
||||
# prevent leaking of tor outbound IP
|
||||
payload += encodeHost('127.0.0.1')
|
||||
payload += pack('>H', 8444)
|
||||
else:
|
||||
|
@ -301,21 +331,25 @@ def assembleVersionMessage(remoteHost, remotePort, participatingStreams, server=
|
|||
(NODE_SSL if haveSSL(server) else 0) |
|
||||
(NODE_DANDELION if state.dandelion else 0)
|
||||
)
|
||||
# = 127.0.0.1. This will be ignored by the remote host. The actual remote connected IP will be used.
|
||||
payload += '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xFF\xFF' + pack('>L', 2130706433)
|
||||
# = 127.0.0.1. This will be ignored by the remote host.
|
||||
# The actual remote connected IP will be used.
|
||||
payload += '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xFF\xFF' + pack(
|
||||
'>L', 2130706433)
|
||||
# we have a separate extPort and incoming over clearnet
|
||||
# or outgoing through clearnet
|
||||
extport = BMConfigParser().safeGetInt('bitmessagesettings', 'extport')
|
||||
if (
|
||||
extport and ((server and not checkSocksIP(remoteHost)) or (
|
||||
BMConfigParser().get('bitmessagesettings', 'socksproxytype') ==
|
||||
'none' and not server))
|
||||
BMConfigParser().get('bitmessagesettings', 'socksproxytype')
|
||||
== 'none' and not server))
|
||||
):
|
||||
payload += pack('>H', extport)
|
||||
elif checkSocksIP(remoteHost) and server: # incoming connection over Tor
|
||||
payload += pack('>H', BMConfigParser().getint('bitmessagesettings', 'onionport'))
|
||||
payload += pack(
|
||||
'>H', BMConfigParser().getint('bitmessagesettings', 'onionport'))
|
||||
else: # no extport and not incoming over Tor
|
||||
payload += pack('>H', BMConfigParser().getint('bitmessagesettings', 'port'))
|
||||
payload += pack(
|
||||
'>H', BMConfigParser().getint('bitmessagesettings', 'port'))
|
||||
|
||||
if nodeid is not None:
|
||||
payload += nodeid[0:8]
|
||||
|
@ -339,7 +373,10 @@ def assembleVersionMessage(remoteHost, remotePort, participatingStreams, server=
|
|||
|
||||
|
||||
def assembleErrorMessage(fatal=0, banTime=0, inventoryVector='', errorText=''):
|
||||
"""Construct the payload of an error message, return the resultng bytes of running CreatePacket() on it"""
|
||||
"""
|
||||
Construct the payload of an error message,
|
||||
return the resulting bytes of running `CreatePacket` on it
|
||||
"""
|
||||
payload = encodeVarint(fatal)
|
||||
payload += encodeVarint(banTime)
|
||||
payload += encodeVarint(len(inventoryVector))
|
||||
|
@ -476,7 +513,7 @@ def decryptAndCheckPubkeyPayload(data, address):
|
|||
except Exception:
|
||||
logger.critical(
|
||||
'Pubkey decryption was UNsuccessful because of'
|
||||
' an unhandled exception! This is definitely a bug! \n%s',
|
||||
traceback.format_exc()
|
||||
' an unhandled exception! This is definitely a bug!',
|
||||
exc_info=True
|
||||
)
|
||||
return 'failed'
|
||||
|
|
|
@ -80,7 +80,9 @@ def isAddressInMySubscriptionsList(address):
|
|||
|
||||
|
||||
def isAddressInMyAddressBookSubscriptionsListOrWhitelist(address):
|
||||
"""Am I subscribed to this address, is it in my addressbook or whitelist?"""
|
||||
"""
|
||||
Am I subscribed to this address, is it in my addressbook or whitelist?
|
||||
"""
|
||||
if isAddressInMyAddressBook(address):
|
||||
return True
|
||||
|
||||
|
@ -100,8 +102,12 @@ def isAddressInMyAddressBookSubscriptionsListOrWhitelist(address):
|
|||
return False
|
||||
|
||||
|
||||
def decodeWalletImportFormat(WIFstring): # pylint: disable=inconsistent-return-statements
|
||||
"""Convert private key from base58 that's used in the config file to 8-bit binary string"""
|
||||
def decodeWalletImportFormat(WIFstring):
|
||||
# pylint: disable=inconsistent-return-statements
|
||||
"""
|
||||
Convert private key from base58 that's used in the config file to
|
||||
8-bit binary string
|
||||
"""
|
||||
fullString = arithmetic.changebase(WIFstring, 58, 256)
|
||||
privkey = fullString[:-4]
|
||||
if fullString[-4:] != \
|
||||
|
@ -126,14 +132,15 @@ def decodeWalletImportFormat(WIFstring): # pylint: disable=inconsistent-retur
|
|||
|
||||
|
||||
def reloadMyAddressHashes():
|
||||
"""Reinitialise runtime data (e.g. encryption objects, address hashes) from the config file"""
|
||||
"""Reload keys for user's addresses from the config file"""
|
||||
logger.debug('reloading keys from keys.dat file')
|
||||
myECCryptorObjects.clear()
|
||||
myAddressesByHash.clear()
|
||||
myAddressesByTag.clear()
|
||||
# myPrivateKeys.clear()
|
||||
|
||||
keyfileSecure = checkSensitiveFilePermissions(state.appdata + 'keys.dat')
|
||||
keyfileSecure = checkSensitiveFilePermissions(os.path.join(
|
||||
state.appdata, 'keys.dat'))
|
||||
hasEnabledKeys = False
|
||||
for addressInKeysFile in BMConfigParser().addresses():
|
||||
isEnabled = BMConfigParser().getboolean(addressInKeysFile, 'enabled')
|
||||
|
@ -162,11 +169,15 @@ def reloadMyAddressHashes():
|
|||
)
|
||||
|
||||
if not keyfileSecure:
|
||||
fixSensitiveFilePermissions(state.appdata + 'keys.dat', hasEnabledKeys)
|
||||
fixSensitiveFilePermissions(os.path.join(
|
||||
state.appdata, 'keys.dat'), hasEnabledKeys)
|
||||
|
||||
|
||||
def reloadBroadcastSendersForWhichImWatching():
|
||||
"""Reinitialise runtime data for the broadcasts I'm subscribed to from the config file"""
|
||||
"""
|
||||
Reinitialize runtime data for the broadcasts I'm subscribed to
|
||||
from the config file
|
||||
"""
|
||||
broadcastSendersForWhichImWatching.clear()
|
||||
MyECSubscriptionCryptorObjects.clear()
|
||||
queryreturn = sqlQuery('SELECT address FROM subscriptions where enabled=1')
|
||||
|
|
|
@ -16,7 +16,9 @@ from queues import (
|
|||
|
||||
|
||||
def doCleanShutdown():
|
||||
"""Used to tell proof of work worker threads and the objectProcessorThread to exit."""
|
||||
"""
|
||||
Used to tell all the treads to finish work and exit.
|
||||
"""
|
||||
state.shutdown = 1
|
||||
|
||||
objectProcessorQueue.put(('checkShutdownVariable', 'no data'))
|
||||
|
@ -52,9 +54,11 @@ def doCleanShutdown():
|
|||
time.sleep(.25)
|
||||
|
||||
for thread in threading.enumerate():
|
||||
if (thread is not threading.currentThread() and
|
||||
isinstance(thread, StoppableThread) and
|
||||
thread.name != 'SQL'):
|
||||
if (
|
||||
thread is not threading.currentThread()
|
||||
and isinstance(thread, StoppableThread)
|
||||
and thread.name != 'SQL'
|
||||
):
|
||||
logger.debug("Waiting for thread %s", thread.name)
|
||||
thread.join()
|
||||
|
||||
|
|
|
@ -16,8 +16,8 @@ appdata = ''
|
|||
|
||||
shutdown = 0
|
||||
"""
|
||||
Set to 1 by the doCleanShutdown function.
|
||||
Used to tell the proof of work worker threads to exit.
|
||||
Set to 1 by the `.shutdown.doCleanShutdown` function.
|
||||
Used to tell the threads to exit.
|
||||
"""
|
||||
|
||||
# Component control flags - set on startup, do not change during runtime
|
||||
|
@ -25,7 +25,7 @@ shutdown = 0
|
|||
enableNetwork = True
|
||||
"""enable network threads"""
|
||||
enableObjProc = True
|
||||
"""enable object processing threads"""
|
||||
"""enable object processing thread"""
|
||||
enableAPI = True
|
||||
"""enable API (if configured)"""
|
||||
enableGUI = True
|
||||
|
@ -35,7 +35,7 @@ enableSTDIO = False
|
|||
curses = False
|
||||
|
||||
sqlReady = False
|
||||
"""set to true by sqlTread when ready for processing"""
|
||||
"""set to true by `.threads.sqlThread` when ready for processing"""
|
||||
|
||||
maximumNumberOfHalfOpenConnections = 0
|
||||
|
||||
|
|
Reference in New Issue
Block a user