* bumped nacl and unpaddedbase32 verison

* added/improved support for unpaddedbase32 keys
* greatly improved home UI and mail
* deniable blocks shouldnt use forward secrecy anymore
* dont add yourself as a contact
This commit is contained in:
Kevin Froman 2019-06-19 01:57:13 -05:00
parent cb2e803ae8
commit 8082570b7f
18 changed files with 117 additions and 55 deletions

View File

@ -27,5 +27,5 @@ def insert_deniable_block(comm_inst):
# This assumes on the libsodium primitives to have key-privacy # This assumes on the libsodium primitives to have key-privacy
fakePeer = onionrvalues.DENIABLE_PEER_ADDRESS fakePeer = onionrvalues.DENIABLE_PEER_ADDRESS
data = secrets.token_hex(secrets.randbelow(1024) + 1) data = secrets.token_hex(secrets.randbelow(1024) + 1)
comm_inst._core.insertBlock(data, header='pm', encryptType='asym', asymPeer=fakePeer, meta={'subject': 'foo'}) comm_inst._core.insertBlock(data, header='pm', encryptType='asym', asymPeer=fakePeer, disableForward=True, meta={'subject': 'foo'})
comm_inst.decrementThreadCount('insert_deniable_block') comm_inst.decrementThreadCount('insert_deniable_block')

View File

@ -128,7 +128,8 @@ class Core:
''' '''
Adds a public key to the key database (misleading function name) Adds a public key to the key database (misleading function name)
''' '''
assert peerID not in self.listPeers() if peerID in self.listPeers() or peerID == self._crypto.pubKey:
raise ValueError("specified id is already known")
# This function simply adds a peer to the DB # This function simply adds a peer to the DB
if not self._utils.validatePubKey(peerID): if not self._utils.validatePubKey(peerID):
@ -776,7 +777,11 @@ class Core:
data = self._crypto.pubKeyEncrypt(data, asymPeer, encodedData=True).decode() data = self._crypto.pubKeyEncrypt(data, asymPeer, encodedData=True).decode()
signature = self._crypto.pubKeyEncrypt(signature, asymPeer, encodedData=True).decode() signature = self._crypto.pubKeyEncrypt(signature, asymPeer, encodedData=True).decode()
signer = self._crypto.pubKeyEncrypt(signer, asymPeer, encodedData=True).decode() signer = self._crypto.pubKeyEncrypt(signer, asymPeer, encodedData=True).decode()
onionrusers.OnionrUser(self, asymPeer, saveUser=True) try:
onionrusers.OnionrUser(self, asymPeer, saveUser=True)
except ValueError:
# if peer is already known
pass
else: else:
raise onionrexceptions.InvalidPubkey(asymPeer + ' is not a valid base32 encoded ed25519 key') raise onionrexceptions.InvalidPubkey(asymPeer + ' is not a valid base32 encoded ed25519 key')

View File

@ -26,7 +26,8 @@ friends = Blueprint('friends', __name__)
@friends.route('/friends/list') @friends.route('/friends/list')
def list_friends(): def list_friends():
pubkey_list = {} pubkey_list = {}
friend_list = contactmanager.ContactManager.list_friends(core.Core()) c = core.Core()
friend_list = contactmanager.ContactManager.list_friends(c)
for friend in friend_list: for friend in friend_list:
pubkey_list[friend.publicKey] = {'name': friend.get_info('name')} pubkey_list[friend.publicKey] = {'name': friend.get_info('name')}
return json.dumps(pubkey_list) return json.dumps(pubkey_list)

View File

@ -21,6 +21,7 @@
import sys, getpass import sys, getpass
import logger, onionrexceptions import logger, onionrexceptions
from onionrusers import onionrusers, contactmanager from onionrusers import onionrusers, contactmanager
import unpaddedbase32
def add_ID(o_inst): def add_ID(o_inst):
try: try:
sys.argv[2] sys.argv[2]
@ -50,6 +51,7 @@ def add_ID(o_inst):
def change_ID(o_inst): def change_ID(o_inst):
try: try:
key = sys.argv[2] key = sys.argv[2]
key = unpaddedbase32.repad(key.encode()).decode()
except IndexError: except IndexError:
logger.warn('Specify pubkey to use') logger.warn('Specify pubkey to use')
else: else:

View File

@ -19,6 +19,7 @@
''' '''
import os, binascii, base64, hashlib, time, sys, hmac, secrets import os, binascii, base64, hashlib, time, sys, hmac, secrets
import nacl.signing, nacl.encoding, nacl.public, nacl.hash, nacl.pwhash, nacl.utils, nacl.secret import nacl.signing, nacl.encoding, nacl.public, nacl.hash, nacl.pwhash, nacl.utils, nacl.secret
import unpaddedbase32
import logger, onionrproofs import logger, onionrproofs
import onionrexceptions, keymanager, core import onionrexceptions, keymanager, core
import config import config
@ -93,6 +94,7 @@ class OnionrCrypto:
def pubKeyEncrypt(self, data, pubkey, encodedData=False): def pubKeyEncrypt(self, data, pubkey, encodedData=False):
'''Encrypt to a public key (Curve25519, taken from base32 Ed25519 pubkey)''' '''Encrypt to a public key (Curve25519, taken from base32 Ed25519 pubkey)'''
pubkey = unpaddedbase32.repad(self._core._utils.strToBytes(pubkey))
retVal = '' retVal = ''
box = None box = None
data = self._core._utils.strToBytes(data) data = self._core._utils.strToBytes(data)
@ -129,7 +131,7 @@ class OnionrCrypto:
return decrypted return decrypted
def symmetricEncrypt(self, data, key, encodedKey=False, returnEncoded=True): def symmetricEncrypt(self, data, key, encodedKey=False, returnEncoded=True):
'''Encrypt data to a 32-byte key (Salsa20-Poly1305 MAC)''' '''Encrypt data with a 32-byte key (Salsa20-Poly1305 MAC)'''
if encodedKey: if encodedKey:
encoding = nacl.encoding.Base64Encoder encoding = nacl.encoding.Base64Encoder
else: else:
@ -199,7 +201,7 @@ class OnionrCrypto:
if pubkey == '': if pubkey == '':
pubkey = self.pubKey pubkey = self.pubKey
prev = '' prev = ''
pubkey = pubkey.encode() pubkey = self._core._utils.strToBytes(pubkey)
for i in range(self.HASH_ID_ROUNDS): for i in range(self.HASH_ID_ROUNDS):
try: try:
prev = prev.encode() prev = prev.encode()

View File

@ -18,10 +18,12 @@
along with this program. If not, see <https://www.gnu.org/licenses/>. along with this program. If not, see <https://www.gnu.org/licenses/>.
''' '''
import os, json, onionrexceptions import os, json, onionrexceptions
import unpaddedbase32
from onionrusers import onionrusers from onionrusers import onionrusers
class ContactManager(onionrusers.OnionrUser): class ContactManager(onionrusers.OnionrUser):
def __init__(self, coreInst, publicKey, saveUser=False, recordExpireSeconds=5): def __init__(self, coreInst, publicKey, saveUser=False, recordExpireSeconds=5):
publicKey = unpaddedbase32.repad(coreInst._utils.strToBytes(publicKey)).decode()
super(ContactManager, self).__init__(coreInst, publicKey, saveUser=saveUser) super(ContactManager, self).__init__(coreInst, publicKey, saveUser=saveUser)
self.dataDir = coreInst.dataDir + '/contacts/' self.dataDir = coreInst.dataDir + '/contacts/'
self.dataFile = '%s/contacts/%s.json' % (coreInst.dataDir, publicKey) self.dataFile = '%s/contacts/%s.json' % (coreInst.dataDir, publicKey)

View File

@ -18,6 +18,7 @@
along with this program. If not, see <https://www.gnu.org/licenses/>. along with this program. If not, see <https://www.gnu.org/licenses/>.
''' '''
import onionrblockapi, logger, onionrexceptions, json, sqlite3, time import onionrblockapi, logger, onionrexceptions, json, sqlite3, time
import unpaddedbase32
import nacl.exceptions import nacl.exceptions
def deleteExpiredKeys(coreInst): def deleteExpiredKeys(coreInst):
@ -55,8 +56,7 @@ class OnionrUser:
Takes an instance of onionr core, a base32 encoded ed25519 public key, and a bool saveUser Takes an instance of onionr core, a base32 encoded ed25519 public key, and a bool saveUser
saveUser determines if we should add a user to our peer database or not. saveUser determines if we should add a user to our peer database or not.
''' '''
if ' ' in coreInst._utils.bytesToStr(publicKey).strip(): publicKey = unpaddedbase32.repad(coreInst._utils.strToBytes(publicKey)).decode()
publicKey = coreInst._utils.convertHumanReadableID(publicKey)
self.trust = 0 self.trust = 0
self._core = coreInst self._core = coreInst
@ -190,6 +190,7 @@ class OnionrUser:
return list(keyList) return list(keyList)
def addForwardKey(self, newKey, expire=DEFAULT_KEY_EXPIRE): def addForwardKey(self, newKey, expire=DEFAULT_KEY_EXPIRE):
newKey = self._core._utils.bytesToStr(unpaddedbase32.repad(self._core._utils.strToBytes(newKey)))
if not self._core._utils.validatePubKey(newKey): if not self._core._utils.validatePubKey(newKey):
# Do not add if something went wrong with the key # Do not add if something went wrong with the key
raise onionrexceptions.InvalidPubkey(newKey) raise onionrexceptions.InvalidPubkey(newKey)

View File

@ -21,6 +21,7 @@
import sys, os, sqlite3, binascii, time, base64, json, glob, shutil, math, re, urllib.parse, string import sys, os, sqlite3, binascii, time, base64, json, glob, shutil, math, re, urllib.parse, string
import requests import requests
import nacl.signing, nacl.encoding import nacl.signing, nacl.encoding
import unpaddedbase32
from onionrblockapi import Block from onionrblockapi import Block
import onionrexceptions, config, logger import onionrexceptions, config, logger
from onionr import API_VERSION from onionr import API_VERSION
@ -319,9 +320,12 @@ class OnionrUtils:
''' '''
Validate if a string is a valid base32 encoded Ed25519 key Validate if a string is a valid base32 encoded Ed25519 key
''' '''
retVal = False
if type(key) is type(None): if type(key) is type(None):
return False return False
# Accept keys that have no = padding
key = unpaddedbase32.repad(self.strToBytes(key))
retVal = False
try: try:
nacl.signing.SigningKey(seed=key, encoder=nacl.encoding.Base32Encoder) nacl.signing.SigningKey(seed=key, encoder=nacl.encoding.Base32Encoder)
except nacl.exceptions.ValueError: except nacl.exceptions.ValueError:

View File

@ -113,4 +113,8 @@ input{
color: black; color: black;
font-size: 1.5em; font-size: 1.5em;
width: 10%; width: 10%;
}
.content{
min-height: 1000px;
} }

View File

@ -58,9 +58,9 @@ function openReply(bHash, quote, subject){
// Add quoted reply // Add quoted reply
var splitQuotes = quote.split('\n') var splitQuotes = quote.split('\n')
for (var x = 0; x < splitQuotes.length; x++){ for (var x = 0; x < splitQuotes.length; x++){
splitQuotes[x] = '>' + splitQuotes[x] splitQuotes[x] = '> ' + splitQuotes[x]
} }
quote = '\n' + splitQuotes.join('\n') quote = '\n' + key.substring(0, 12) + ' wrote:' + '\n' + splitQuotes.join('\n')
document.getElementById('draftText').value = quote document.getElementById('draftText').value = quote
setActiveTab('send message') setActiveTab('send message')
} }
@ -77,7 +77,7 @@ function openThread(bHash, sender, date, sigBool, pubkey, subjectLine){
var sigMsg = 'signature' var sigMsg = 'signature'
// show add unknown contact button if peer is unknown but still has pubkey // show add unknown contact button if peer is unknown but still has pubkey
if (sender == pubkey){ if (sender === pubkey && sender !== myPub){
addUnknownContact.style.display = 'inline' addUnknownContact.style.display = 'inline'
} }

View File

@ -54,6 +54,11 @@ sendForm.onsubmit = function(){
return false return false
} }
} }
sendMail(to.value, messageContent.value, subject.value) if (to.value.length !== 56 && to.value.length !== 52){
alert('Public key is not valid')
}
else{
sendMail(to.value, messageContent.value, subject.value)
}
return false return false
} }

View File

@ -21,26 +21,26 @@
<br><br> <br><br>
<div>🕵️‍♂️ Current Used Identity: <input class='myPub' type='text' readonly></div> <div>🕵️‍♂️ Current Used Identity: <input class='myPub' type='text' readonly></div>
<br> <br>
<button id='shutdownNode' class='warnBtn'>Shutdown Node</button> <button id='refreshStats' class='primaryBtn'>Refresh Stats</button> <button id='shutdownNode' class='btn warnBtn'>Shutdown Node</button> <button id='refreshStats' class='btn primaryBtn'>Refresh Stats</button>
<br><br> <br><br>
<h1>Onionr Services</h1>
<label>Open Site: <input type='text' id='siteViewer' placeholder='Site Hash'> <button id='openSite' class='primaryBtn openSiteBtn'>Open Onionr Site</button></label> <label>Open Site: <input type='text' id='siteViewer' placeholder='Site Hash'> <button id='openSite' class='primaryBtn openSiteBtn'>Open Onionr Site</button></label>
<br> <br>
<br><br><a class='idLink' href='/mail/'>Mail</a> - <a class='idLink' href='/friends/'>Friend Manager</a> - <a class='idLink' href='/board/'>Boards</a> - <br><br><a class='idLink' href='/mail/'>Mail</a> - <a class='idLink' href='/friends/'>Friend Manager</a> - <a class='idLink' href='/board/'>Circle</a> -
<a class='idLink' href='/clandestine/'>Clandestine</a> <a class='idLink' href='/clandestine/'>Clandestine</a>
<br><br><hr> <br><br><hr>
<details class='configArea'> <details class='configArea'>
<summary><b>Edit Configuration</b></summary> <summary><b>Edit Configuration</b></summary>
<br> <br>
<p><em>Warning: </em><b>Some values can be dangerous to change. Use caution.</b></p> <p><em>Warning: </em><b>Some values can be dangerous to change.<br><br>Configuration contains sensitive information.</b></p>
<br> <br>
<textarea class='configEditor'></textarea> <textarea class='configEditor'></textarea>
<button class='saveConfig successBtn'>Save Config</button> <button class='saveConfig successBtn'>Save Config</button>
</details> </details>
<hr> <hr>
<h1>Statistics</h1> <p>🔒 Security Level: <span id='securityLevel'></span></p>
<p>🕰️ Uptime: <span id='uptime'></span></p> <p>🕰️ Uptime: <span id='uptime'></span></p>
<h2>Connections</h2> <h2>Connections</h2>
<p class='secRequestNotice hidden'>Note: on high security levels, you should have <em>no</em> received requests.</p>
<p>🖇️ Last Received Request: <span id='lastIncoming'>None since start</span></p> <p>🖇️ Last Received Request: <span id='lastIncoming'>None since start</span></p>
<p>⬇️ Total Requests Received: <span id='totalRec'>None since start</span></p> <p>⬇️ Total Requests Received: <span id='totalRec'>None since start</span></p>
<p>🔗 Outgoing Connections:</p> <p>🔗 Outgoing Connections:</p>

View File

@ -5,4 +5,14 @@
.saveConfig{ .saveConfig{
margin-top: 1em; margin-top: 1em;
}
.idLink{
-webkit-touch-callout: none; /* iOS Safari */
-webkit-user-select: none; /* Safari */
-moz-user-select: none; /* Firefox */
-ms-user-select: none; /* Internet Explorer/Edge */
user-select: none; /* Non-prefixed version, currently
supported by Chrome and Opera */
} }

View File

@ -18,10 +18,34 @@
*/ */
uptimeDisplay = document.getElementById('uptime') uptimeDisplay = document.getElementById('uptime')
connectedDisplay = document.getElementById('connectedNodes') connectedDisplay = document.getElementById('connectedNodes')
connectedDisplay.style.maxHeight = '300px'
connectedDisplay.style.overflowY = 'scroll'
storedBlockDisplay = document.getElementById('storedBlocks') storedBlockDisplay = document.getElementById('storedBlocks')
queuedBlockDisplay = document.getElementById('blockQueue') queuedBlockDisplay = document.getElementById('blockQueue')
lastIncoming = document.getElementById('lastIncoming') lastIncoming = document.getElementById('lastIncoming')
totalRec = document.getElementById('totalRec') totalRec = document.getElementById('totalRec')
securityLevel = document.getElementById('securityLevel')
sec_description_str = 'unknown'
function showSecStatNotice(){
var secWarnEls = document.getElementsByClassName('secRequestNotice')
for (el = 0; el < secWarnEls.length; el++){
secWarnEls[el].style.display = 'block'
}
}
switch (httpGet('/config/get/general.security_level')){
case "0":
sec_description_str = 'normal'
break;
case "1":
sec_description_str = 'high'
break;
}
if (sec_description_str !== 'normal'){
showSecStatNotice()
}
function getStats(){ function getStats(){
stats = JSON.parse(httpGet('getstats', webpass)) stats = JSON.parse(httpGet('getstats', webpass))
@ -29,6 +53,7 @@ function getStats(){
connectedDisplay.innerText = stats['connectedNodes'] connectedDisplay.innerText = stats['connectedNodes']
storedBlockDisplay.innerText = stats['blockCount'] storedBlockDisplay.innerText = stats['blockCount']
queuedBlockDisplay.innerText = stats['blockQueueCount'] queuedBlockDisplay.innerText = stats['blockQueueCount']
securityLevel.innerText = sec_description_str
totalRec.innerText = httpGet('/hitcount') totalRec.innerText = httpGet('/hitcount')
var lastConnect = httpGet('/lastconnect') var lastConnect = httpGet('/lastconnect')
if (lastConnect > 0){ if (lastConnect > 0){

View File

@ -178,8 +178,16 @@ body{
background-color:#396BAC; background-color:#396BAC;
} }
.btn:hover{
opacity: 0.6;
}
.openSiteBtn{ .openSiteBtn{
padding: 5px; padding: 5px;
border: 1px solid black; border: 1px solid black;
border-radius: 5px; border-radius: 5px;
} }
.hidden{
display: none;
}

View File

@ -29,11 +29,13 @@ class OnionrValidations(unittest.TestCase):
def test_pubkey_validator(self): def test_pubkey_validator(self):
# Test ed25519 public key validity # Test ed25519 public key validity
valid = 'JZ5VE72GUS3C7BOHDRIYZX4B5U5EJMCMLKHLYCVBQQF3UKHYIRRQ====' valids = ['JZ5VE72GUS3C7BOHDRIYZX4B5U5EJMCMLKHLYCVBQQF3UKHYIRRQ====', 'JZ5VE72GUS3C7BOHDRIYZX4B5U5EJMCMLKHLYCVBQQF3UKHYIRRQ']
invalid = [None, '', ' ', 'dfsg', '\n', 'JZ5VE72GUS3C7BOHDRIYZX4B5U5EJMCMLKHLYCVBQQF3UKHYIR$Q===='] invalid = [None, '', ' ', 'dfsg', '\n', 'JZ5VE72GUS3C7BOHDRIYZX4B5U5EJMCMLKHLYCVBQQF3UKHYIR$Q====']
c = core.Core() c = core.Core()
print('testing', valid)
self.assertTrue(c._utils.validatePubKey(valid)) for valid in valids:
print('testing', valid)
self.assertTrue(c._utils.validatePubKey(valid))
for x in invalid: for x in invalid:
#print('testing', x) #print('testing', x)

View File

@ -1,9 +1,10 @@
urllib3==1.24.2 urllib3==1.24.2
requests==2.21.0 requests==2.21.0
PyNaCl==1.2.1 PyNaCl==1.3.0
gevent==1.3.6 gevent==1.3.6
Flask==1.0.2 Flask==1.0.2
PySocks==1.6.8 PySocks==1.6.8
stem==1.7.1 stem==1.7.1
deadsimplekv==0.1.1 deadsimplekv==0.1.1
unpaddedbase32==0.1.0
jinja2==2.10.1 jinja2==2.10.1

View File

@ -2,7 +2,7 @@
# This file is autogenerated by pip-compile # This file is autogenerated by pip-compile
# To update, run: # To update, run:
# #
# pip-compile --generate-hashes --output-file requirements.txt requirements.in # pip-compile --generate-hashes --output-file=requirements.txt requirements.in
# #
certifi==2018.11.29 \ certifi==2018.11.29 \
--hash=sha256:47f9c83ef4c0c621eaef743f133f09fa8a74a9b75f037e8624f83bd1b6626cb7 \ --hash=sha256:47f9c83ef4c0c621eaef743f133f09fa8a74a9b75f037e8624f83bd1b6626cb7 \
@ -140,38 +140,26 @@ markupsafe==1.1.1 \
pycparser==2.19 \ pycparser==2.19 \
--hash=sha256:a988718abfad80b6b157acce7bf130a30876d27603738ac39f140993246b25b3 \ --hash=sha256:a988718abfad80b6b157acce7bf130a30876d27603738ac39f140993246b25b3 \
# via cffi # via cffi
pynacl==1.2.1 \ pynacl==1.3.0 \
--hash=sha256:04e30e5bdeeb2d5b34107f28cd2f5bbfdc6c616f3be88fc6f53582ff1669eeca \ --hash=sha256:05c26f93964373fc0abe332676cb6735f0ecad27711035b9472751faa8521255 \
--hash=sha256:0bfa0d94d2be6874e40f896e0a67e290749151e7de767c5aefbad1121cad7512 \ --hash=sha256:0c6100edd16fefd1557da078c7a31e7b7d7a52ce39fdca2bec29d4f7b6e7600c \
--hash=sha256:11aa4e141b2456ce5cecc19c130e970793fa3a2c2e6fbb8ad65b28f35aa9e6b6 \ --hash=sha256:0d0a8171a68edf51add1e73d2159c4bc19fc0718e79dec51166e940856c2f28e \
--hash=sha256:13bdc1fe084ff9ac7653ae5a924cae03bf4bb07c6667c9eb5b6eb3c570220776 \ --hash=sha256:1c780712b206317a746ace34c209b8c29dbfd841dfbc02aa27f2084dd3db77ae \
--hash=sha256:14339dc233e7a9dda80a3800e64e7ff89d0878ba23360eea24f1af1b13772cac \ --hash=sha256:2424c8b9f41aa65bbdbd7a64e73a7450ebb4aa9ddedc6a081e7afcc4c97f7621 \
--hash=sha256:1d33e775fab3f383167afb20b9927aaf4961b953d76eeb271a5703a6d756b65b \ --hash=sha256:2d23c04e8d709444220557ae48ed01f3f1086439f12dbf11976e849a4926db56 \
--hash=sha256:2a42b2399d0428619e58dac7734838102d35f6dcdee149e0088823629bf99fbb \ --hash=sha256:30f36a9c70450c7878053fa1344aca0145fd47d845270b43a7ee9192a051bf39 \
--hash=sha256:2dce05ac8b3c37b9e2f65eab56c544885607394753e9613fd159d5e2045c2d98 \ --hash=sha256:37aa336a317209f1bb099ad177fef0da45be36a2aa664507c5d72015f956c310 \
--hash=sha256:63cfccdc6217edcaa48369191ae4dca0c390af3c74f23c619e954973035948cd \ --hash=sha256:4943decfc5b905748f0756fdd99d4f9498d7064815c4cf3643820c9028b711d1 \
--hash=sha256:6453b0dae593163ffc6db6f9c9c1597d35c650598e2c39c0590d1757207a1ac2 \ --hash=sha256:57ef38a65056e7800859e5ba9e6091053cd06e1038983016effaffe0efcd594a \
--hash=sha256:73a5a96fb5fbf2215beee2353a128d382dbca83f5341f0d3c750877a236569ef \ --hash=sha256:5bd61e9b44c543016ce1f6aef48606280e45f892a928ca7068fba30021e9b786 \
--hash=sha256:8abb4ef79161a5f58848b30ab6fb98d8c466da21fdd65558ce1d7afc02c70b5f \ --hash=sha256:6482d3017a0c0327a49dddc8bd1074cc730d45db2ccb09c3bac1f8f32d1eb61b \
--hash=sha256:8ac1167195b32a8755de06efd5b2d2fe76fc864517dab66aaf65662cc59e1988 \ --hash=sha256:7d3ce02c0784b7cbcc771a2da6ea51f87e8716004512493a2b69016326301c3b \
--hash=sha256:8f505f42f659012794414fa57c498404e64db78f1d98dfd40e318c569f3c783b \ --hash=sha256:a14e499c0f5955dcc3991f785f3f8e2130ed504fa3a7f44009ff458ad6bdd17f \
--hash=sha256:9c8a06556918ee8e3ab48c65574f318f5a0a4d31437fc135da7ee9d4f9080415 \ --hash=sha256:a39f54ccbcd2757d1d63b0ec00a00980c0b382c62865b61a505163943624ab20 \
--hash=sha256:a1e25fc5650cf64f01c9e435033e53a4aca9de30eb9929d099f3bb078e18f8f2 \ --hash=sha256:aabb0c5232910a20eec8563503c153a8e78bbf5459490c49ab31f6adf3f3a415 \
--hash=sha256:be71cd5fce04061e1f3d39597f93619c80cdd3558a6c9ba99a546f144a8d8101 \ --hash=sha256:bd4ecb473a96ad0f90c20acba4f0bf0df91a4e03a1f4dd6a4bdc9ca75aa3a715 \
--hash=sha256:c5b1a7a680218dee9da0f1b5e24072c46b3c275d35712bc1d505b85bb03441c0 \ --hash=sha256:e2da3c13307eac601f3de04887624939aca8ee3c9488a0bb0eca4fb9401fc6b1 \
--hash=sha256:cb785db1a9468841a1265c9215c60fe5d7af2fb1b209e3316a152704607fc582 \ --hash=sha256:f67814c38162f4deb31f68d590771a29d5ae3b1bd64b75cf232308e5c74777e0
--hash=sha256:cf6877124ae6a0698404e169b3ba534542cfbc43f939d46b927d956daf0a373a \
--hash=sha256:d0eb5b2795b7ee2cbcfcadacbe95a13afbda048a262bd369da9904fecb568975 \
--hash=sha256:d3a934e2b9f20abac009d5b6951067cfb5486889cb913192b4d8288b216842f1 \
--hash=sha256:d795f506bcc9463efb5ebb0f65ed77921dcc9e0a50499dedd89f208445de9ecb \
--hash=sha256:d8aaf7e5d6b0e0ef7d6dbf7abeb75085713d0100b4eb1a4e4e857de76d77ac45 \
--hash=sha256:de2aaca8386cf4d70f1796352f2346f48ddb0bed61dc43a3ce773ba12e064031 \
--hash=sha256:e0d38fa0a75f65f556fb912f2c6790d1fa29b7dd27a1d9cc5591b281321eaaa9 \
--hash=sha256:eb2acabbd487a46b38540a819ef67e477a674481f84a82a7ba2234b9ba46f752 \
--hash=sha256:eeee629828d0eb4f6d98ac41e9a3a6461d114d1d0aa111a8931c049359298da0 \
--hash=sha256:f5836463a3c0cca300295b229b6c7003c415a9d11f8f9288ddbd728e2746524c \
--hash=sha256:f5ce9e26d25eb0b2d96f3ef0ad70e1d3ae89b5d60255c462252a3e456a48c053 \
--hash=sha256:fabf73d5d0286f9e078774f3435601d2735c94ce9e514ac4fb945701edead7e4
pysocks==1.6.8 \ pysocks==1.6.8 \
--hash=sha256:3fe52c55890a248676fd69dc9e3c4e811718b777834bcaab7a8125cf9deac672 --hash=sha256:3fe52c55890a248676fd69dc9e3c4e811718b777834bcaab7a8125cf9deac672
requests==2.21.0 \ requests==2.21.0 \
@ -183,6 +171,8 @@ six==1.12.0 \
# via pynacl # via pynacl
stem==1.7.1 \ stem==1.7.1 \
--hash=sha256:c9eaf3116cb60c15995cbd3dec3a5cbc50e9bb6e062c4d6d42201e566f498ca2 --hash=sha256:c9eaf3116cb60c15995cbd3dec3a5cbc50e9bb6e062c4d6d42201e566f498ca2
unpaddedbase32==0.1.0 \
--hash=sha256:5e4143fcaf77c9c6b4f60d18301c7570f0dac561dcf9b9aed8b5ba6ead7f218c
urllib3==1.24.2 \ urllib3==1.24.2 \
--hash=sha256:4c291ca23bbb55c76518905869ef34bdd5f0e46af7afe6861e8375643ffee1a0 \ --hash=sha256:4c291ca23bbb55c76518905869ef34bdd5f0e46af7afe6861e8375643ffee1a0 \
--hash=sha256:9a247273df709c4fedb38c711e44292304f73f39ab01beda9f6b9fc375669ac3 --hash=sha256:9a247273df709c4fedb38c711e44292304f73f39ab01beda9f6b9fc375669ac3