def process_block_metadata(blockHash: str): ''' Read metadata from a block and cache it to the block database blockHash -> sha3_256 hex formatted hash of Onionr block ''' curTime = epoch.get_rounded_epoch(roundS=60) myBlock = onionrblockapi.Block(blockHash) if myBlock.isEncrypted: myBlock.decrypt() if (myBlock.isEncrypted and myBlock.decrypted) or (not myBlock.isEncrypted): blockType = myBlock.getMetadata( 'type' ) # we would use myBlock.getType() here, but it is bugged with encrypted blocks signer = bytesconverter.bytes_to_str(myBlock.signer) valid = myBlock.verifySig() if valid: if myBlock.getMetadata('newFSKey') is not None: try: onionrusers.OnionrUser(signer).addForwardKey( myBlock.getMetadata('newFSKey')) except onionrexceptions.InvalidPubkey: logger.warn( '%s has invalid forward secrecy key to add: %s' % (signer, myBlock.getMetadata('newFSKey'))) try: if len(blockType) <= onionrvalues.MAX_BLOCK_TYPE_LENGTH: blockmetadb.update_block_info(blockHash, 'dataType', blockType) except TypeError: logger.warn("Missing block information") pass # Set block expire time if specified try: expireTime = int(myBlock.getHeader('expire')) # test that expire time is an integer of sane length (for epoch) # doesn't matter if its too large because of the min() func below if not len(str(expireTime)) < 20: raise ValueError('timestamp invalid') except (ValueError, TypeError) as e: expireTime = onionrvalues.DEFAULT_EXPIRE + curTime finally: expireTime = min(expireTime, curTime + onionrvalues.DEFAULT_EXPIRE) blockmetadb.update_block_info(blockHash, 'expire', expireTime) if blockType == 'update': updater.update_event(myBlock) onionrevents.event('processblocks', data={ 'block': myBlock, 'type': blockType, 'signer': signer, 'validSig': valid })
def friend_command(o_inst): friend = '' try: # Get the friend command action = sys.argv[2] except IndexError: logger.info('Syntax: friend add/remove/list [address]') else: action = action.lower() if action == 'list': # List out peers marked as our friend for friend in onionrusers.OnionrUser.list_friends( o_inst.onionrCore): logger.info(friend.publicKey + ' - ' + friend.getName()) elif action in ('add', 'remove'): try: friend = sys.argv[3] if not o_inst.onionrUtils.validatePubKey(friend): raise onionrexceptions.InvalidPubkey( 'Public key is invalid') if friend not in o_inst.onionrCore.listPeers(): raise onionrexceptions.KeyNotKnown friend = onionrusers.OnionrUser(o_inst.onionrCore, friend) except IndexError: logger.warn('Friend ID is required.') action = 'error' # set to 'error' so that the finally block does not process anything except onionrexceptions.KeyNotKnown: o_inst.onionrCore.addPeer(friend) friend = onionrusers.OnionrUser(o_inst.onionrCore, friend) finally: if action == 'add': friend.setTrust(1) logger.info('Added %s as friend.' % (friend.publicKey, )) elif action == 'remove': friend.setTrust(0) logger.info('Removed %s as friend.' % (friend.publicKey, )) else: logger.info('Syntax: friend add/remove/list [address]')
def processBlockMetadata(self, blockHash): ''' Read metadata from a block and cache it to the block database ''' curTime = self.getRoundedEpoch(roundS=60) myBlock = Block(blockHash, self._core) if myBlock.isEncrypted: myBlock.decrypt() if (myBlock.isEncrypted and myBlock.decrypted) or (not myBlock.isEncrypted): blockType = myBlock.getMetadata( 'type' ) # we would use myBlock.getType() here, but it is bugged with encrypted blocks signer = self.bytesToStr(myBlock.signer) valid = myBlock.verifySig() if myBlock.getMetadata('newFSKey') is not None: onionrusers.OnionrUser(self._core, signer).addForwardKey( myBlock.getMetadata('newFSKey')) try: if len(blockType) <= 10: self._core.updateBlockInfo(blockHash, 'dataType', blockType) except TypeError: logger.warn("Missing block information") pass # Set block expire time if specified try: expireTime = myBlock.getHeader('expire') assert len( str(int(expireTime)) ) < 20 # test that expire time is an integer of sane length (for epoch) except (AssertionError, ValueError, TypeError) as e: expireTime = onionrvalues.OnionrValues( ).default_expire + curTime finally: self._core.updateBlockInfo(blockHash, 'expire', expireTime) if not blockType is None: self._core.updateBlockInfo(blockHash, 'dataType', blockType) onionrevents.event('processblocks', data={ 'block': myBlock, 'type': blockType, 'signer': signer, 'validSig': valid }, onionr=self._core.onionrInst) else: pass
def test_forward_encrypt_bin(self): friend = crypto.generate() friendUser = onionrusers.OnionrUser(friend[0], saveUser=True) for x in range(5): message = os.urandom(32) forwardKey = friendUser.generateForwardKey() fakeForwardPair = crypto.generate() self.assertTrue(friendUser.addForwardKey(fakeForwardPair[0])) encrypted = friendUser.forwardEncrypt(message) decrypted = crypto.encryption.pub_key_decrypt(encrypted[0], privkey=fakeForwardPair[1], encodedData=True) self.assertEqual(decrypted, message)
def test_forward_encrypt(self): friend = crypto.generate() friendUser = onionrusers.OnionrUser(friend[0], saveUser=True) for x in range(5): message = 'hello world %s' % (random.randint(1, 1000)) forwardKey = friendUser.generateForwardKey() fakeForwardPair = crypto.generate() self.assertTrue(friendUser.addForwardKey(fakeForwardPair[0])) encrypted = friendUser.forwardEncrypt(message) decrypted = crypto.encryption.pub_key_decrypt(encrypted[0], privkey=fakeForwardPair[1], encodedData=True) self.assertEqual(decrypted, message.encode()) return
def test_forward_encrypt(self): os.environ["ONIONR_HOME"] = TEST_DIR_1 o = onionr.Onionr() friend = o.onionrCore._crypto.generatePubKey() friendUser = onionrusers.OnionrUser(o.onionrCore, friend[0], saveUser=True) for x in range(5): message = 'hello world %s' % (random.randint(1, 1000)) forwardKey = friendUser.generateForwardKey() fakeForwardPair = o.onionrCore._crypto.generatePubKey() self.assertTrue(friendUser.addForwardKey(fakeForwardPair[0])) encrypted = friendUser.forwardEncrypt(message) decrypted = o.onionrCore._crypto.pubKeyDecrypt( encrypted[0], privkey=fakeForwardPair[1], encodedData=True) self.assertEqual(decrypted, message.encode()) return
def test_users(self): keypair = c._crypto.generatePubKey() onionrusers.OnionrUser(c, keypair[0]) return
def insertBlock(self, data, header='txt', sign=False, encryptType='', symKey='', asymPeer='', meta={}, expire=None, disableForward=False): ''' Inserts a block into the network encryptType must be specified to encrypt a block ''' allocationReachedMessage = 'Cannot insert block, disk allocation reached.' if self._utils.storageCounter.isFull(): logger.error(allocationReachedMessage) return False retData = False if type(data) is None: raise ValueError('Data cannot be none') createTime = self._utils.getRoundedEpoch() # check nonce #print(data) dataNonce = self._utils.bytesToStr(self._crypto.sha3Hash(data)) try: with open(self.dataNonceFile, 'r') as nonces: if dataNonce in nonces: return retData except FileNotFoundError: pass # record nonce with open(self.dataNonceFile, 'a') as nonceFile: nonceFile.write(dataNonce + '\n') if type(data) is bytes: data = data.decode() data = str(data) plaintext = data plaintextMeta = {} plaintextPeer = asymPeer retData = '' signature = '' signer = '' metadata = {} # metadata is full block metadata, meta is internal, user specified metadata # only use header if not set in provided meta meta['type'] = str(header) if encryptType in ('asym', 'sym', ''): metadata['encryptType'] = encryptType else: raise onionrexceptions.InvalidMetadata( 'encryptType must be asym or sym, or blank') try: data = data.encode() except AttributeError: pass if encryptType == 'asym': meta[ 'rply'] = createTime # Duplicate the time in encrypted messages to prevent replays if not disableForward and sign and asymPeer != self._crypto.pubKey: try: forwardEncrypted = onionrusers.OnionrUser( self, asymPeer).forwardEncrypt(data) data = forwardEncrypted[0] meta['forwardEnc'] = True expire = forwardEncrypted[ 2] # Expire time of key. no sense keeping block after that except onionrexceptions.InvalidPubkey: pass #onionrusers.OnionrUser(self, asymPeer).generateForwardKey() fsKey = onionrusers.OnionrUser(self, asymPeer).generateForwardKey() #fsKey = onionrusers.OnionrUser(self, asymPeer).getGeneratedForwardKeys().reverse() meta['newFSKey'] = fsKey jsonMeta = json.dumps(meta) plaintextMeta = jsonMeta if sign: signature = self._crypto.edSign(jsonMeta.encode() + data, key=self._crypto.privKey, encodeResult=True) signer = self._crypto.pubKey if len(jsonMeta) > 1000: raise onionrexceptions.InvalidMetadata( 'meta in json encoded form must not exceed 1000 bytes') user = onionrusers.OnionrUser(self, symKey) # encrypt block metadata/sig/content if encryptType == 'sym': if len(symKey) < self.requirements.passwordLength: raise onionrexceptions.SecurityError('Weak encryption key') jsonMeta = self._crypto.symmetricEncrypt( jsonMeta, key=symKey, returnEncoded=True).decode() data = self._crypto.symmetricEncrypt(data, key=symKey, returnEncoded=True).decode() signature = self._crypto.symmetricEncrypt( signature, key=symKey, returnEncoded=True).decode() signer = self._crypto.symmetricEncrypt( signer, key=symKey, returnEncoded=True).decode() elif encryptType == 'asym': if self._utils.validatePubKey(asymPeer): # Encrypt block data with forward secrecy key first, but not meta jsonMeta = json.dumps(meta) jsonMeta = self._crypto.pubKeyEncrypt( jsonMeta, asymPeer, encodedData=True).decode() data = self._crypto.pubKeyEncrypt(data, asymPeer, encodedData=True).decode() signature = self._crypto.pubKeyEncrypt( signature, asymPeer, encodedData=True).decode() signer = self._crypto.pubKeyEncrypt(signer, asymPeer, encodedData=True).decode() onionrusers.OnionrUser(self, asymPeer, saveUser=True) else: raise onionrexceptions.InvalidPubkey( asymPeer + ' is not a valid base32 encoded ed25519 key') # compile metadata metadata['meta'] = jsonMeta metadata['sig'] = signature metadata['signer'] = signer metadata['time'] = createTime # ensure expire is integer and of sane length if type(expire) is not type(None): assert len(str(int(expire))) < 14 metadata['expire'] = expire # send block data (and metadata) to POW module to get tokenized block data if self.use_subprocess: payload = subprocesspow.SubprocessPOW(data, metadata, self).start() else: payload = onionrproofs.POW(metadata, data).waitForResult() if payload != False: try: retData = self.setData(payload) except onionrexceptions.DiskAllocationReached: logger.error(allocationReachedMessage) retData = False else: # Tell the api server through localCommand to wait for the daemon to upload this block to make statistical analysis more difficult if self._utils.localCommand('/ping', maxWait=10) == 'pong!': self._utils.localCommand('/waitforshare/' + retData, post=True, maxWait=5) self.daemonQueueAdd('uploadBlock', retData) self.addToBlockDB(retData, selfInsert=True, dataSaved=True) self._utils.processBlockMetadata(retData) if retData != False: if plaintextPeer == onionrvalues.DENIABLE_PEER_ADDRESS: events.event('insertdeniable', { 'content': plaintext, 'meta': plaintextMeta, 'hash': retData, 'peer': self._utils.bytesToStr(asymPeer) }, onionr=self.onionrInst, threaded=True) else: events.event('insertblock', { 'content': plaintext, 'meta': plaintextMeta, 'hash': retData, 'peer': self._utils.bytesToStr(asymPeer) }, onionr=self.onionrInst, threaded=True) return retData
def decrypt(self, encodedData=True): ''' Decrypt a block, loading decrypted data into their vars ''' if self.decrypted: return True retData = False core = self.getCore() # decrypt data if self.getHeader('encryptType') == 'asym': try: self.bcontent = core._crypto.pubKeyDecrypt( self.bcontent, encodedData=encodedData) bmeta = core._crypto.pubKeyDecrypt(self.bmetadata, encodedData=encodedData) try: bmeta = bmeta.decode() except AttributeError: # yet another bytes fix pass self.bmetadata = json.loads(bmeta) self.signature = core._crypto.pubKeyDecrypt( self.signature, encodedData=encodedData) self.signer = core._crypto.pubKeyDecrypt( self.signer, encodedData=encodedData) self.bheader['signer'] = self.signer.decode() self.signedData = json.dumps( self.bmetadata) + self.bcontent.decode() # Check for replay attacks try: if self.core._utils.getEpoch() - self.core.getBlockDate( self.hash) < 60: assert self.core._crypto.replayTimestampValidation( self.bmetadata['rply']) except (AssertionError, KeyError, TypeError) as e: if not self.bypassReplayCheck: # Zero out variables to prevent reading of replays self.bmetadata = {} self.signer = '' self.bheader['signer'] = '' self.signedData = '' self.signature = '' raise onionrexceptions.ReplayAttack( 'Signature is too old. possible replay attack') try: assert self.bmetadata['forwardEnc'] is True except (AssertionError, KeyError) as e: pass else: try: self.bcontent = onionrusers.OnionrUser( self.getCore(), self.signer).forwardDecrypt(self.bcontent) except (onionrexceptions.DecryptionError, nacl.exceptions.CryptoError) as e: logger.error(str(e)) pass except nacl.exceptions.CryptoError: pass #logger.debug('Could not decrypt block. Either invalid key or corrupted data') except onionrexceptions.ReplayAttack: logger.warn('%s is possibly a replay attack' % (self.hash, )) else: retData = True self.decrypted = True else: logger.warn( 'symmetric decryption is not yet supported by this API') return retData
def insert_block(data: Union[str, bytes], header: str = 'txt', sign: bool = False, encryptType: str = '', symKey: str = '', asymPeer: str = '', meta: dict = {}, expire: Union[int, None] = None, disableForward: bool = False, signing_key: UserIDSecretKey = '') -> Union[str, bool]: """ Create and insert a block into the network. encryptType must be specified to encrypt a block if expire is less than date, assumes seconds into future. if not assume exact epoch """ our_private_key = crypto.priv_key our_pub_key = crypto.pub_key storage_counter = storagecounter.StorageCounter() allocationReachedMessage = 'Cannot insert block, disk allocation reached.' if storage_counter.is_full(): logger.error(allocationReachedMessage) raise onionrexceptions.DiskAllocationReached if signing_key != '': # if it was specified to use an alternative private key our_private_key = signing_key our_pub_key = bytesconverter.bytes_to_str( crypto.cryptoutils.get_pub_key_from_priv(our_private_key)) retData = False if type(data) is None: raise ValueError('Data cannot be none') createTime = epoch.get_epoch() dataNonce = bytesconverter.bytes_to_str(crypto.hashers.sha3_hash(data)) try: with open(filepaths.data_nonce_file, 'r') as nonces: if dataNonce in nonces: return retData except FileNotFoundError: pass # record nonce with open(filepaths.data_nonce_file, 'a') as nonceFile: nonceFile.write(dataNonce + '\n') plaintext = data plaintextMeta = {} plaintextPeer = asymPeer retData = '' signature = '' signer = '' metadata = {} # metadata is full block metadata # meta is internal, user specified metadata # only use header if not set in provided meta meta['type'] = str(header) if encryptType in ('asym', 'sym'): metadata['encryptType'] = encryptType else: if not config.get('general.store_plaintext_blocks', True): raise onionrexceptions.InvalidMetadata( "Plaintext blocks are disabled, " + "yet a plaintext block was being inserted") if encryptType not in ('', None): raise onionrexceptions.InvalidMetadata( 'encryptType must be asym or sym, or blank') try: data = data.encode() except AttributeError: pass if encryptType == 'asym': # Duplicate the time in encrypted messages to help prevent replays meta['rply'] = createTime if sign and asymPeer != our_pub_key: try: forwardEncrypted = onionrusers.OnionrUser( asymPeer).forwardEncrypt(data) data = forwardEncrypted[0] meta['forwardEnc'] = True # Expire time of key. no sense keeping block after that expire = forwardEncrypted[2] except onionrexceptions.InvalidPubkey: pass if not disableForward: fsKey = onionrusers.OnionrUser(asymPeer).generateForwardKey() meta['newFSKey'] = fsKey jsonMeta = json.dumps(meta) plaintextMeta = jsonMeta if sign: signature = crypto.signing.ed_sign(jsonMeta.encode() + data, key=our_private_key, encodeResult=True) signer = our_pub_key if len(jsonMeta) > 1000: raise onionrexceptions.InvalidMetadata( 'meta in json encoded form must not exceed 1000 bytes') # encrypt block metadata/sig/content if encryptType == 'sym': raise NotImplementedError("not yet implemented") elif encryptType == 'asym': if stringvalidators.validate_pub_key(asymPeer): # Encrypt block data with forward secrecy key first, but not meta jsonMeta = json.dumps(meta) jsonMeta = crypto.encryption.pub_key_encrypt( jsonMeta, asymPeer, encodedData=True).decode() data = crypto.encryption.pub_key_encrypt(data, asymPeer, encodedData=False) signature = crypto.encryption.pub_key_encrypt( signature, asymPeer, encodedData=True).decode() signer = crypto.encryption.pub_key_encrypt( signer, asymPeer, encodedData=True).decode() try: onionrusers.OnionrUser(asymPeer, saveUser=True) except ValueError: # if peer is already known pass else: raise onionrexceptions.InvalidPubkey( asymPeer + ' is not a valid base32 encoded ed25519 key') # compile metadata metadata['meta'] = jsonMeta if len(signature) > 0: # I don't like not pattern metadata['sig'] = signature metadata['signer'] = signer metadata['time'] = createTime # ensure expire is integer and of sane length if type(expire) is not type(None): # noqa if not len(str(int(expire))) < 20: raise ValueError( 'expire must be valid int less than 20 digits in length') # if expire is less than date, assume seconds into future if expire < epoch.get_epoch(): expire = epoch.get_epoch() + expire metadata['expire'] = expire # send block data (and metadata) to POW module to get tokenized block data payload = subprocesspow.SubprocessPOW(data, metadata).start() if payload != False: # noqa try: retData = onionrstorage.set_data(payload) except onionrexceptions.DiskAllocationReached: logger.error(allocationReachedMessage) retData = False else: if disableForward: logger.warn( f'{retData} asym encrypted block created w/o ephemerality') """ Tell the api server through localCommand to wait for the daemon to upload this block to make statistical analysis more difficult """ spawn(localcommand.local_command, '/daemon-event/upload_event', post=True, is_json=True, post_data={ 'block': retData }).get(timeout=5) coredb.blockmetadb.add.add_to_block_DB(retData, selfInsert=True, dataSaved=True) if expire is None: coredb.blockmetadb.update_block_info( retData, 'expire', createTime + min( onionrvalues.DEFAULT_EXPIRE, config.get('general.max_block_age', onionrvalues.DEFAULT_EXPIRE))) else: coredb.blockmetadb.update_block_info(retData, 'expire', expire) blockmetadata.process_block_metadata(retData) if retData != False: # noqa if plaintextPeer == onionrvalues.DENIABLE_PEER_ADDRESS: events.event('insertdeniable', { 'content': plaintext, 'meta': plaintextMeta, 'hash': retData, 'peer': bytesconverter.bytes_to_str(asymPeer) }, threaded=True) else: events.event('insertblock', { 'content': plaintext, 'meta': plaintextMeta, 'hash': retData, 'peer': bytesconverter.bytes_to_str(asymPeer) }, threaded=True) spawn(localcommand.local_command, '/daemon-event/remove_from_insert_queue_wrapper', post=True, post_data={ 'block_hash': retData }, is_json=True).get(timeout=5) return retData
def decrypt(self, encodedData=True): """ Decrypt a block, loading decrypted data into their vars """ if self.decrypted: return True retData = False # decrypt data if self.getHeader('encryptType') == 'asym': try: try: self.bcontent = encryption.pub_key_decrypt( self.bcontent, encodedData=encodedData) except (binascii.Error, ValueError) as e: self.bcontent = encryption.pub_key_decrypt( self.bcontent, encodedData=False) bmeta = encryption.pub_key_decrypt(self.bmetadata, encodedData=encodedData) try: bmeta = bmeta.decode() except AttributeError: # yet another bytes fix pass self.bmetadata = json.loads(bmeta) self.signature = encryption.pub_key_decrypt( self.signature, encodedData=encodedData) self.signer = encryption.pub_key_decrypt( self.signer, encodedData=encodedData) self.bheader['signer'] = self.signer.decode() self.signedData = json.dumps( self.bmetadata).encode() + self.bcontent if not self.signer is None: if not self.verifySig(): raise onionrexceptions.SignatureError( "Block has invalid signature") # Check for replay attacks try: if epoch.get_epoch() - blockmetadb.get_block_date( self.hash) > 60: if not cryptoutils.replay_validator( self.bmetadata['rply']): raise onionrexceptions.ReplayAttack except (AssertionError, KeyError, TypeError, onionrexceptions.ReplayAttack) as e: if not self.bypassReplayCheck: # Zero out variables to prevent reading of replays self.bmetadata = {} self.signer = '' self.bheader['signer'] = '' self.signedData = '' self.signature = '' raise onionrexceptions.ReplayAttack( 'Signature is too old. possible replay attack') try: if not self.bmetadata['forwardEnc']: raise KeyError except (AssertionError, KeyError) as e: pass else: try: self.bcontent = onionrusers.OnionrUser( self.signer).forwardDecrypt(self.bcontent) except (onionrexceptions.DecryptionError, nacl.exceptions.CryptoError) as e: #logger.error(str(e)) pass except nacl.exceptions.CryptoError: pass #logger.debug('Could not decrypt block. Either invalid key or corrupted data') except onionrexceptions.ReplayAttack: logger.warn('%s is possibly a replay attack' % (self.hash, )) else: retData = True self.decrypted = True return retData
def test_is_friend(self): contact = crypto.generate()[0] contact = onionrusers.OnionrUser(contact, saveUser=True) self.assertFalse(contact.isFriend()) contact.setTrust(1) self.assertTrue(contact.isFriend())
def test_users(self): keypair = crypto.generate() onionrusers.OnionrUser(keypair[0])
def inbox(self): blockCount = 0 pmBlockMap = {} pmBlocks = {} logger.info('Decrypting messages...') choice = '' displayList = [] subject = '' # this could use a lot of memory if someone has received a lot of messages for blockHash in self.myCore.getBlocksByType('pm'): pmBlocks[blockHash] = Block(blockHash, core=self.myCore) pmBlocks[blockHash].decrypt() blockCount = 0 for blockHash in pmBlocks: if not pmBlocks[blockHash].decrypted: continue blockCount += 1 pmBlockMap[blockCount] = blockHash block = pmBlocks[blockHash] senderKey = block.signer try: senderKey = senderKey.decode() except AttributeError: pass senderDisplay = onionrusers.OnionrUser(self.myCore, senderKey).getName() if senderDisplay == 'anonymous': senderDisplay = senderKey blockDate = pmBlocks[blockHash].getDate().strftime("%m/%d %H:%M") try: subject = pmBlocks[blockHash].bmetadata['subject'] except KeyError: subject = '' displayList.append('%s. %s - %s - <%s>: %s' % (blockCount, blockDate, senderDisplay[:12], subject[:10], blockHash)) while choice not in ('-q', 'q', 'quit'): for i in displayList: logger.info(i) try: choice = logger.readline( 'Enter a block number, -r to refresh, or -q to stop: ' ).strip().lower() except (EOFError, KeyboardInterrupt): choice = '-q' if choice in ('-q', 'q', 'quit'): continue if choice in ('-r', 'r', 'refresh'): # dirty hack self.inbox() return try: choice = int(choice) except ValueError: pass else: try: pmBlockMap[choice] readBlock = pmBlocks[pmBlockMap[choice]] except KeyError: pass else: cancel = '' readBlock.verifySig() senderDisplay = self.myCore._utils.bytesToStr( readBlock.signer) if len(senderDisplay.strip()) == 0: senderDisplay = 'Anonymous' logger.info('Message received from %s' % (senderDisplay, )) logger.info('Valid signature: %s' % readBlock.validSig) if not readBlock.validSig: logger.warn( 'This message has an INVALID/NO signature. ANYONE could have sent this message.' ) cancel = logger.readline( 'Press enter to continue to message, or -q to not open the message (recommended).' ) print('') if cancel != '-q': try: print( draw_border( self.myCore._utils.escapeAnsi( readBlock.bcontent.decode().strip()))) except ValueError: logger.warn( 'Error presenting message. This is usually due to a malformed or blank message.' ) pass if readBlock.validSig: reply = logger.readline( "Press enter to continue, or enter %s to reply" % ("-r", )) print('') if reply == "-r": self.draft_message( self.myCore._utils.bytesToStr( readBlock.signer, )) else: logger.readline("Press enter to continue") print('') return