Module onionr.onionrblocks.onionrblockapi

Onionr - P2P Anonymous Storage Network

This file contains the OnionrBlocks class which is a class for working with Onionr blocks

Source code
'''
    Onionr - P2P Anonymous Storage Network

    This file contains the OnionrBlocks class which is a class for working with Onionr blocks
'''
'''
    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <https://www.gnu.org/licenses/>.
'''
import binascii
import logger, config, onionrexceptions, nacl.exceptions
import json, os, sys, datetime, base64, onionrstorage
from onionrusers import onionrusers
from onionrutils import stringvalidators, epoch
from coredb import blockmetadb
from onionrstorage import removeblock
import onionrblocks
from onionrcrypto import encryption, cryptoutils as cryptoutils, signing
class Block:
    blockCacheOrder = list() # NEVER write your own code that writes to this!
    blockCache = dict() # should never be accessed directly, look at Block.getCache()

    def __init__(self, hash = None, type = None, content = None, expire=None, decrypt=False, bypassReplayCheck=False):
        # take from arguments
        # sometimes people input a bytes object instead of str in `hash`
        if (not hash is None) and isinstance(hash, bytes):
            hash = hash.decode()

        self.hash = hash
        self.btype = type
        self.bcontent = content
        self.expire = expire
        self.bypassReplayCheck = bypassReplayCheck

        # initialize variables
        self.valid = True
        self.raw = None
        self.signed = False
        self.signature = None
        self.signedData = None
        self.blockFile = None
        self.bheader = {}
        self.bmetadata = {}
        self.isEncrypted = False
        self.decrypted = False
        self.signer = None
        self.validSig = False
        self.autoDecrypt = decrypt

        self.update()

    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 verifySig(self):
        '''
            Verify if a block's signature is signed by its claimed signer
        '''
        if self.signer is None or signing.ed_verify(data=self.signedData, key=self.signer, sig=self.signature, encodedData=True):
            self.validSig = True
        else:
            self.validSig = False
        return self.validSig

    def update(self, data = None, file = None):
        '''
            Loads data from a block in to the current object.

            Inputs:
            - data (str):
              - if None: will load from file by hash
              - else: will load from `data` string
            - file (str):
              - if None: will load from file specified in this parameter
              - else: will load from wherever block is stored by hash

            Outputs:
            - (bool): indicates whether or not the operation was successful
        '''
        try:
            # import from string
            blockdata = data

            # import from file
            if blockdata is None:
                try:
                    blockdata = onionrstorage.getData(self.getHash())#.decode()
                except AttributeError:
                    raise onionrexceptions.NoDataAvailable('Block does not exist')
            else:
                self.blockFile = None
            # parse block
            self.raw = blockdata
            self.bheader = json.loads(self.getRaw()[:self.getRaw().index(b'\n')])
            self.bcontent = self.getRaw()[self.getRaw().index(b'\n') + 1:]
            if ('encryptType' in self.bheader) and (self.bheader['encryptType'] in ('asym', 'sym')):
                self.bmetadata = self.getHeader('meta', None)
                self.isEncrypted = True
            else:
                self.bmetadata = json.loads(self.getHeader('meta', None))
            self.btype = self.getMetadata('type', None)
            self.signed = ('sig' in self.getHeader() and self.getHeader('sig') != '')
            # TODO: detect if signer is hash of pubkey or not
            self.signer = self.getHeader('signer', None)
            self.signature = self.getHeader('sig', None)
            # signed data is jsonMeta + block content (no linebreak)
            self.signedData = (None if not self.isSigned() else self.getHeader('meta') + self.getContent())
            self.date = blockmetadb.get_block_date(self.getHash())
            self.claimedTime = self.getHeader('time', None)

            if not self.getDate() is None:
                self.date = datetime.datetime.fromtimestamp(self.getDate())

            self.valid = True
            
            if self.autoDecrypt:
                self.decrypt()

            return True
        except Exception as e:
            logger.warn('Failed to parse block %s' % self.getHash(), error = e, timestamp = False)

            # if block can't be parsed, it's a waste of precious space. Throw it away.
            if not self.delete():
                logger.warn('Failed to delete invalid block %s.' % self.getHash(), error = e)
            else:
                logger.debug('Deleted invalid block %s.' % self.getHash(), timestamp = False)

        self.valid = False
        return False

    def delete(self):
        '''
            Deletes the block's file and records, if they exist

            Outputs:
            - (bool): whether or not the operation was successful
        '''

        if self.exists():
            try:
                os.remove(self.getBlockFile())
            except TypeError:
                pass
            b_hash = self.getHash()
            onionrstorage.deleteBlock(b_hash)
            removeblock.remove_block(b_hash)
            return True
        return False

    def save(self, sign = False, recreate = True):
        '''
            Saves a block to file and imports it into Onionr

            Inputs:
            - sign (bool): whether or not to sign the block before saving
            - recreate (bool): if the block already exists, whether or not to recreate the block and save under a new hash

            Outputs:
            - (bool): whether or not the operation was successful
        '''

        try:
            if self.isValid() is True:

                self.hash = onionrblocks.insert(self.getRaw(), header = self.getType(), sign = sign, meta = self.getMetadata(), expire = self.getExpire())
                if self.hash != False:
                    self.update()

                return self.getHash()
            else:
                logger.warn('Not writing block; it is invalid.')
        except Exception as e:
            logger.error('Failed to save block.', error = e, timestamp = False)

        return False

    # getters

    def getExpire(self):
        '''
            Returns the expire time for a block

            Outputs:
            - (int): the expire time for a block, or None
        '''
        return self.expire

    def getHash(self):
        '''
            Returns the hash of the block if saved to file

            Outputs:
            - (str): the hash of the block, or None
        '''

        return self.hash

    def getType(self):
        '''
            Returns the type of the block

            Outputs:
            - (str): the type of the block
        '''
        return self.btype

    def getRaw(self):
        '''
            Returns the raw contents of the block, if saved to file

            Outputs:
            - (bytes): the raw contents of the block, or None
        '''

        return self.raw

    def getHeader(self, key = None, default = None):
        '''
            Returns the header information

            Inputs:
            - key (str): only returns the value of the key in the header

            Outputs:
            - (dict/str): either the whole header as a dict, or one value
        '''

        if not key is None:
            if key in self.getHeader():
                return self.getHeader()[key]
            return default
        return self.bheader

    def getMetadata(self, key = None, default = None):
        '''
            Returns the metadata information

            Inputs:
            - key (str): only returns the value of the key in the metadata

            Outputs:
            - (dict/str): either the whole metadata as a dict, or one value
        '''

        if not key is None:
            if key in self.getMetadata():
                return self.getMetadata()[key]
            return default
        return self.bmetadata

    def getContent(self):
        '''
            Returns the contents of the block

            Outputs:
            - (str): the contents of the block
        '''

        return str(self.bcontent)

    def getDate(self):
        '''
            Returns the date that the block was received, if loaded from file

            Outputs:
            - (datetime): the date that the block was received
        '''

        return self.date

    def getBlockFile(self):
        '''
            Returns the location of the block file if it is saved

            Outputs:
            - (str): the location of the block file, or None
        '''

        return self.blockFile

    def isValid(self):
        '''
            Checks if the block is valid

            Outputs:
            - (bool): whether or not the block is valid
        '''

        return self.valid

    def isSigned(self):
        '''
            Checks if the block was signed

            Outputs:
            - (bool): whether or not the block is signed
        '''

        return self.signed

    def getSignature(self):
        '''
            Returns the base64-encoded signature

            Outputs:
            - (str): the signature, or None
        '''

        return self.signature

    def getSignedData(self):
        '''
            Returns the data that was signed

            Outputs:
            - (str): the data that was signed, or None
        '''

        return self.signedData

    def isSigner(self, signer, encodedData = True):
        '''
            Checks if the block was signed by the signer inputted

            Inputs:
            - signer (str): the public key of the signer to check against
            - encodedData (bool): whether or not the `signer` argument is base64 encoded

            Outputs:
            - (bool): whether or not the signer of the block is the signer inputted
        '''

        try:
            if (not self.isSigned()) or (not stringvalidators.validate_pub_key(signer)):
                return False

            return bool(signing.ed_verify(self.getSignedData(), signer, self.getSignature(), encodedData = encodedData))
        except:
            return False

    # setters

    def setType(self, btype):
        '''
            Sets the type of the block

            Inputs:
            - btype (str): the type of block to be set to

            Outputs:
            - (Block): the Block instance
        '''

        self.btype = btype
        return self

    def setMetadata(self, key, val):
        '''
            Sets a custom metadata value

            Metadata should not store block-specific data structures.

            Inputs:
            - key (str): the key
            - val: the value (type is irrelevant)

            Outputs:
            - (Block): the Block instance
        '''

        self.bmetadata[key] = val
        return self

    def setContent(self, bcontent):
        '''
            Sets the contents of the block

            Inputs:
            - bcontent (str): the contents to be set to

            Outputs:
            - (Block): the Block instance
        '''

        self.bcontent = str(bcontent)
        return self

    # static functions

    def getBlocks(type = None, signer = None, signed = None, reverse = False, limit = None):
        '''
            Returns a list of Block objects based on supplied filters

            Inputs:
            - type (str): filters by block type
            - signer (str/list): filters by signer (one in the list has to be a signer)
            - signed (bool): filters out by whether or not the block is signed
            - reverse (bool): reverses the list if True

            Outputs:
            - (list): a list of Block objects that match the input
        '''

        try:

            relevant_blocks = list()
            blocks = (blockmetadb.get_block_list() if type is None else blockmetadb.get_blocks_by_type(type))

            for block in blocks:
                if Block.exists(block):
                    block = Block(block)

                    relevant = True

                    if (not signed is None) and (block.isSigned() != bool(signed)):
                        relevant = False
                    if not signer is None:
                        if isinstance(signer, (str,)):
                            signer = [signer]
                        if isinstance(signer, (bytes,)):
                            signer = [signer.decode()]

                        isSigner = False
                        for key in signer:
                            if block.isSigner(key):
                                isSigner = True
                                break

                        if not isSigner:
                            relevant = False

                    if relevant and (limit is None or len(relevant_Blocks) <= int(limit)):
                        relevant_blocks.append(block)

            if bool(reverse):
                relevant_blocks.reverse()

            return relevant_blocks
        except Exception as e:
            logger.debug('Failed to get blocks.', error = e)

        return list()

    def exists(bHash):
        '''
            Checks if a block is saved to file or not

            Inputs:
            - hash (str/Block):
              - if (Block): check if this block is saved to file
              - if (str): check if a block by this hash is in file

            Outputs:
            - (bool): whether or not the block file exists
        '''

        # no input data? scrap it.
        if bHash is None:
            return False

        if isinstance(bHash, Block):
            bHash = bHash.getHash()
        
        ret = isinstance(onionrstorage.getData(bHash), type(None))

        return not ret

Classes

class Block (hash=None, type=None, content=None, expire=None, decrypt=False, bypassReplayCheck=False)
Source code
class Block:
    blockCacheOrder = list() # NEVER write your own code that writes to this!
    blockCache = dict() # should never be accessed directly, look at Block.getCache()

    def __init__(self, hash = None, type = None, content = None, expire=None, decrypt=False, bypassReplayCheck=False):
        # take from arguments
        # sometimes people input a bytes object instead of str in `hash`
        if (not hash is None) and isinstance(hash, bytes):
            hash = hash.decode()

        self.hash = hash
        self.btype = type
        self.bcontent = content
        self.expire = expire
        self.bypassReplayCheck = bypassReplayCheck

        # initialize variables
        self.valid = True
        self.raw = None
        self.signed = False
        self.signature = None
        self.signedData = None
        self.blockFile = None
        self.bheader = {}
        self.bmetadata = {}
        self.isEncrypted = False
        self.decrypted = False
        self.signer = None
        self.validSig = False
        self.autoDecrypt = decrypt

        self.update()

    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 verifySig(self):
        '''
            Verify if a block's signature is signed by its claimed signer
        '''
        if self.signer is None or signing.ed_verify(data=self.signedData, key=self.signer, sig=self.signature, encodedData=True):
            self.validSig = True
        else:
            self.validSig = False
        return self.validSig

    def update(self, data = None, file = None):
        '''
            Loads data from a block in to the current object.

            Inputs:
            - data (str):
              - if None: will load from file by hash
              - else: will load from `data` string
            - file (str):
              - if None: will load from file specified in this parameter
              - else: will load from wherever block is stored by hash

            Outputs:
            - (bool): indicates whether or not the operation was successful
        '''
        try:
            # import from string
            blockdata = data

            # import from file
            if blockdata is None:
                try:
                    blockdata = onionrstorage.getData(self.getHash())#.decode()
                except AttributeError:
                    raise onionrexceptions.NoDataAvailable('Block does not exist')
            else:
                self.blockFile = None
            # parse block
            self.raw = blockdata
            self.bheader = json.loads(self.getRaw()[:self.getRaw().index(b'\n')])
            self.bcontent = self.getRaw()[self.getRaw().index(b'\n') + 1:]
            if ('encryptType' in self.bheader) and (self.bheader['encryptType'] in ('asym', 'sym')):
                self.bmetadata = self.getHeader('meta', None)
                self.isEncrypted = True
            else:
                self.bmetadata = json.loads(self.getHeader('meta', None))
            self.btype = self.getMetadata('type', None)
            self.signed = ('sig' in self.getHeader() and self.getHeader('sig') != '')
            # TODO: detect if signer is hash of pubkey or not
            self.signer = self.getHeader('signer', None)
            self.signature = self.getHeader('sig', None)
            # signed data is jsonMeta + block content (no linebreak)
            self.signedData = (None if not self.isSigned() else self.getHeader('meta') + self.getContent())
            self.date = blockmetadb.get_block_date(self.getHash())
            self.claimedTime = self.getHeader('time', None)

            if not self.getDate() is None:
                self.date = datetime.datetime.fromtimestamp(self.getDate())

            self.valid = True
            
            if self.autoDecrypt:
                self.decrypt()

            return True
        except Exception as e:
            logger.warn('Failed to parse block %s' % self.getHash(), error = e, timestamp = False)

            # if block can't be parsed, it's a waste of precious space. Throw it away.
            if not self.delete():
                logger.warn('Failed to delete invalid block %s.' % self.getHash(), error = e)
            else:
                logger.debug('Deleted invalid block %s.' % self.getHash(), timestamp = False)

        self.valid = False
        return False

    def delete(self):
        '''
            Deletes the block's file and records, if they exist

            Outputs:
            - (bool): whether or not the operation was successful
        '''

        if self.exists():
            try:
                os.remove(self.getBlockFile())
            except TypeError:
                pass
            b_hash = self.getHash()
            onionrstorage.deleteBlock(b_hash)
            removeblock.remove_block(b_hash)
            return True
        return False

    def save(self, sign = False, recreate = True):
        '''
            Saves a block to file and imports it into Onionr

            Inputs:
            - sign (bool): whether or not to sign the block before saving
            - recreate (bool): if the block already exists, whether or not to recreate the block and save under a new hash

            Outputs:
            - (bool): whether or not the operation was successful
        '''

        try:
            if self.isValid() is True:

                self.hash = onionrblocks.insert(self.getRaw(), header = self.getType(), sign = sign, meta = self.getMetadata(), expire = self.getExpire())
                if self.hash != False:
                    self.update()

                return self.getHash()
            else:
                logger.warn('Not writing block; it is invalid.')
        except Exception as e:
            logger.error('Failed to save block.', error = e, timestamp = False)

        return False

    # getters

    def getExpire(self):
        '''
            Returns the expire time for a block

            Outputs:
            - (int): the expire time for a block, or None
        '''
        return self.expire

    def getHash(self):
        '''
            Returns the hash of the block if saved to file

            Outputs:
            - (str): the hash of the block, or None
        '''

        return self.hash

    def getType(self):
        '''
            Returns the type of the block

            Outputs:
            - (str): the type of the block
        '''
        return self.btype

    def getRaw(self):
        '''
            Returns the raw contents of the block, if saved to file

            Outputs:
            - (bytes): the raw contents of the block, or None
        '''

        return self.raw

    def getHeader(self, key = None, default = None):
        '''
            Returns the header information

            Inputs:
            - key (str): only returns the value of the key in the header

            Outputs:
            - (dict/str): either the whole header as a dict, or one value
        '''

        if not key is None:
            if key in self.getHeader():
                return self.getHeader()[key]
            return default
        return self.bheader

    def getMetadata(self, key = None, default = None):
        '''
            Returns the metadata information

            Inputs:
            - key (str): only returns the value of the key in the metadata

            Outputs:
            - (dict/str): either the whole metadata as a dict, or one value
        '''

        if not key is None:
            if key in self.getMetadata():
                return self.getMetadata()[key]
            return default
        return self.bmetadata

    def getContent(self):
        '''
            Returns the contents of the block

            Outputs:
            - (str): the contents of the block
        '''

        return str(self.bcontent)

    def getDate(self):
        '''
            Returns the date that the block was received, if loaded from file

            Outputs:
            - (datetime): the date that the block was received
        '''

        return self.date

    def getBlockFile(self):
        '''
            Returns the location of the block file if it is saved

            Outputs:
            - (str): the location of the block file, or None
        '''

        return self.blockFile

    def isValid(self):
        '''
            Checks if the block is valid

            Outputs:
            - (bool): whether or not the block is valid
        '''

        return self.valid

    def isSigned(self):
        '''
            Checks if the block was signed

            Outputs:
            - (bool): whether or not the block is signed
        '''

        return self.signed

    def getSignature(self):
        '''
            Returns the base64-encoded signature

            Outputs:
            - (str): the signature, or None
        '''

        return self.signature

    def getSignedData(self):
        '''
            Returns the data that was signed

            Outputs:
            - (str): the data that was signed, or None
        '''

        return self.signedData

    def isSigner(self, signer, encodedData = True):
        '''
            Checks if the block was signed by the signer inputted

            Inputs:
            - signer (str): the public key of the signer to check against
            - encodedData (bool): whether or not the `signer` argument is base64 encoded

            Outputs:
            - (bool): whether or not the signer of the block is the signer inputted
        '''

        try:
            if (not self.isSigned()) or (not stringvalidators.validate_pub_key(signer)):
                return False

            return bool(signing.ed_verify(self.getSignedData(), signer, self.getSignature(), encodedData = encodedData))
        except:
            return False

    # setters

    def setType(self, btype):
        '''
            Sets the type of the block

            Inputs:
            - btype (str): the type of block to be set to

            Outputs:
            - (Block): the Block instance
        '''

        self.btype = btype
        return self

    def setMetadata(self, key, val):
        '''
            Sets a custom metadata value

            Metadata should not store block-specific data structures.

            Inputs:
            - key (str): the key
            - val: the value (type is irrelevant)

            Outputs:
            - (Block): the Block instance
        '''

        self.bmetadata[key] = val
        return self

    def setContent(self, bcontent):
        '''
            Sets the contents of the block

            Inputs:
            - bcontent (str): the contents to be set to

            Outputs:
            - (Block): the Block instance
        '''

        self.bcontent = str(bcontent)
        return self

    # static functions

    def getBlocks(type = None, signer = None, signed = None, reverse = False, limit = None):
        '''
            Returns a list of Block objects based on supplied filters

            Inputs:
            - type (str): filters by block type
            - signer (str/list): filters by signer (one in the list has to be a signer)
            - signed (bool): filters out by whether or not the block is signed
            - reverse (bool): reverses the list if True

            Outputs:
            - (list): a list of Block objects that match the input
        '''

        try:

            relevant_blocks = list()
            blocks = (blockmetadb.get_block_list() if type is None else blockmetadb.get_blocks_by_type(type))

            for block in blocks:
                if Block.exists(block):
                    block = Block(block)

                    relevant = True

                    if (not signed is None) and (block.isSigned() != bool(signed)):
                        relevant = False
                    if not signer is None:
                        if isinstance(signer, (str,)):
                            signer = [signer]
                        if isinstance(signer, (bytes,)):
                            signer = [signer.decode()]

                        isSigner = False
                        for key in signer:
                            if block.isSigner(key):
                                isSigner = True
                                break

                        if not isSigner:
                            relevant = False

                    if relevant and (limit is None or len(relevant_Blocks) <= int(limit)):
                        relevant_blocks.append(block)

            if bool(reverse):
                relevant_blocks.reverse()

            return relevant_blocks
        except Exception as e:
            logger.debug('Failed to get blocks.', error = e)

        return list()

    def exists(bHash):
        '''
            Checks if a block is saved to file or not

            Inputs:
            - hash (str/Block):
              - if (Block): check if this block is saved to file
              - if (str): check if a block by this hash is in file

            Outputs:
            - (bool): whether or not the block file exists
        '''

        # no input data? scrap it.
        if bHash is None:
            return False

        if isinstance(bHash, Block):
            bHash = bHash.getHash()
        
        ret = isinstance(onionrstorage.getData(bHash), type(None))

        return not ret

Class variables

var blockCache
var blockCacheOrder

Methods

def decrypt(self, encodedData=True)

Decrypt a block, loading decrypted data into their vars

Source code
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 delete(self)

Deletes the block's file and records, if they exist

Outputs: - (bool): whether or not the operation was successful

Source code
def delete(self):
    '''
        Deletes the block's file and records, if they exist

        Outputs:
        - (bool): whether or not the operation was successful
    '''

    if self.exists():
        try:
            os.remove(self.getBlockFile())
        except TypeError:
            pass
        b_hash = self.getHash()
        onionrstorage.deleteBlock(b_hash)
        removeblock.remove_block(b_hash)
        return True
    return False
def exists(bHash)

Checks if a block is saved to file or not

Inputs: - hash (str/Block): - if (Block): check if this block is saved to file - if (str): check if a block by this hash is in file

Outputs: - (bool): whether or not the block file exists

Source code
def exists(bHash):
    '''
        Checks if a block is saved to file or not

        Inputs:
        - hash (str/Block):
          - if (Block): check if this block is saved to file
          - if (str): check if a block by this hash is in file

        Outputs:
        - (bool): whether or not the block file exists
    '''

    # no input data? scrap it.
    if bHash is None:
        return False

    if isinstance(bHash, Block):
        bHash = bHash.getHash()
    
    ret = isinstance(onionrstorage.getData(bHash), type(None))

    return not ret
def getBlockFile(self)

Returns the location of the block file if it is saved

Outputs: - (str): the location of the block file, or None

Source code
def getBlockFile(self):
    '''
        Returns the location of the block file if it is saved

        Outputs:
        - (str): the location of the block file, or None
    '''

    return self.blockFile
def getBlocks(type=None, signer=None, signed=None, reverse=False, limit=None)

Returns a list of Block objects based on supplied filters

Inputs: - type (str): filters by block type - signer (str/list): filters by signer (one in the list has to be a signer) - signed (bool): filters out by whether or not the block is signed - reverse (bool): reverses the list if True

Outputs: - (list): a list of Block objects that match the input

Source code
def getBlocks(type = None, signer = None, signed = None, reverse = False, limit = None):
    '''
        Returns a list of Block objects based on supplied filters

        Inputs:
        - type (str): filters by block type
        - signer (str/list): filters by signer (one in the list has to be a signer)
        - signed (bool): filters out by whether or not the block is signed
        - reverse (bool): reverses the list if True

        Outputs:
        - (list): a list of Block objects that match the input
    '''

    try:

        relevant_blocks = list()
        blocks = (blockmetadb.get_block_list() if type is None else blockmetadb.get_blocks_by_type(type))

        for block in blocks:
            if Block.exists(block):
                block = Block(block)

                relevant = True

                if (not signed is None) and (block.isSigned() != bool(signed)):
                    relevant = False
                if not signer is None:
                    if isinstance(signer, (str,)):
                        signer = [signer]
                    if isinstance(signer, (bytes,)):
                        signer = [signer.decode()]

                    isSigner = False
                    for key in signer:
                        if block.isSigner(key):
                            isSigner = True
                            break

                    if not isSigner:
                        relevant = False

                if relevant and (limit is None or len(relevant_Blocks) <= int(limit)):
                    relevant_blocks.append(block)

        if bool(reverse):
            relevant_blocks.reverse()

        return relevant_blocks
    except Exception as e:
        logger.debug('Failed to get blocks.', error = e)

    return list()
def getContent(self)

Returns the contents of the block

Outputs: - (str): the contents of the block

Source code
def getContent(self):
    '''
        Returns the contents of the block

        Outputs:
        - (str): the contents of the block
    '''

    return str(self.bcontent)
def getDate(self)

Returns the date that the block was received, if loaded from file

Outputs: - (datetime): the date that the block was received

Source code
def getDate(self):
    '''
        Returns the date that the block was received, if loaded from file

        Outputs:
        - (datetime): the date that the block was received
    '''

    return self.date
def getExpire(self)

Returns the expire time for a block

Outputs: - (int): the expire time for a block, or None

Source code
def getExpire(self):
    '''
        Returns the expire time for a block

        Outputs:
        - (int): the expire time for a block, or None
    '''
    return self.expire
def getHash(self)

Returns the hash of the block if saved to file

Outputs: - (str): the hash of the block, or None

Source code
def getHash(self):
    '''
        Returns the hash of the block if saved to file

        Outputs:
        - (str): the hash of the block, or None
    '''

    return self.hash
def getHeader(self, key=None, default=None)

Returns the header information

Inputs: - key (str): only returns the value of the key in the header

Outputs: - (dict/str): either the whole header as a dict, or one value

Source code
def getHeader(self, key = None, default = None):
    '''
        Returns the header information

        Inputs:
        - key (str): only returns the value of the key in the header

        Outputs:
        - (dict/str): either the whole header as a dict, or one value
    '''

    if not key is None:
        if key in self.getHeader():
            return self.getHeader()[key]
        return default
    return self.bheader
def getMetadata(self, key=None, default=None)

Returns the metadata information

Inputs: - key (str): only returns the value of the key in the metadata

Outputs: - (dict/str): either the whole metadata as a dict, or one value

Source code
def getMetadata(self, key = None, default = None):
    '''
        Returns the metadata information

        Inputs:
        - key (str): only returns the value of the key in the metadata

        Outputs:
        - (dict/str): either the whole metadata as a dict, or one value
    '''

    if not key is None:
        if key in self.getMetadata():
            return self.getMetadata()[key]
        return default
    return self.bmetadata
def getRaw(self)

Returns the raw contents of the block, if saved to file

Outputs: - (bytes): the raw contents of the block, or None

Source code
def getRaw(self):
    '''
        Returns the raw contents of the block, if saved to file

        Outputs:
        - (bytes): the raw contents of the block, or None
    '''

    return self.raw
def getSignature(self)

Returns the base64-encoded signature

Outputs: - (str): the signature, or None

Source code
def getSignature(self):
    '''
        Returns the base64-encoded signature

        Outputs:
        - (str): the signature, or None
    '''

    return self.signature
def getSignedData(self)

Returns the data that was signed

Outputs: - (str): the data that was signed, or None

Source code
def getSignedData(self):
    '''
        Returns the data that was signed

        Outputs:
        - (str): the data that was signed, or None
    '''

    return self.signedData
def getType(self)

Returns the type of the block

Outputs: - (str): the type of the block

Source code
def getType(self):
    '''
        Returns the type of the block

        Outputs:
        - (str): the type of the block
    '''
    return self.btype
def isSigned(self)

Checks if the block was signed

Outputs: - (bool): whether or not the block is signed

Source code
def isSigned(self):
    '''
        Checks if the block was signed

        Outputs:
        - (bool): whether or not the block is signed
    '''

    return self.signed
def isSigner(self, signer, encodedData=True)

Checks if the block was signed by the signer inputted

Inputs: - signer (str): the public key of the signer to check against - encodedData (bool): whether or not the signer argument is base64 encoded

Outputs: - (bool): whether or not the signer of the block is the signer inputted

Source code
def isSigner(self, signer, encodedData = True):
    '''
        Checks if the block was signed by the signer inputted

        Inputs:
        - signer (str): the public key of the signer to check against
        - encodedData (bool): whether or not the `signer` argument is base64 encoded

        Outputs:
        - (bool): whether or not the signer of the block is the signer inputted
    '''

    try:
        if (not self.isSigned()) or (not stringvalidators.validate_pub_key(signer)):
            return False

        return bool(signing.ed_verify(self.getSignedData(), signer, self.getSignature(), encodedData = encodedData))
    except:
        return False
def isValid(self)

Checks if the block is valid

Outputs: - (bool): whether or not the block is valid

Source code
def isValid(self):
    '''
        Checks if the block is valid

        Outputs:
        - (bool): whether or not the block is valid
    '''

    return self.valid
def save(self, sign=False, recreate=True)

Saves a block to file and imports it into Onionr

Inputs: - sign (bool): whether or not to sign the block before saving - recreate (bool): if the block already exists, whether or not to recreate the block and save under a new hash

Outputs: - (bool): whether or not the operation was successful

Source code
def save(self, sign = False, recreate = True):
    '''
        Saves a block to file and imports it into Onionr

        Inputs:
        - sign (bool): whether or not to sign the block before saving
        - recreate (bool): if the block already exists, whether or not to recreate the block and save under a new hash

        Outputs:
        - (bool): whether or not the operation was successful
    '''

    try:
        if self.isValid() is True:

            self.hash = onionrblocks.insert(self.getRaw(), header = self.getType(), sign = sign, meta = self.getMetadata(), expire = self.getExpire())
            if self.hash != False:
                self.update()

            return self.getHash()
        else:
            logger.warn('Not writing block; it is invalid.')
    except Exception as e:
        logger.error('Failed to save block.', error = e, timestamp = False)

    return False
def setContent(self, bcontent)

Sets the contents of the block

Inputs: - bcontent (str): the contents to be set to

Outputs: - (Block): the Block instance

Source code
def setContent(self, bcontent):
    '''
        Sets the contents of the block

        Inputs:
        - bcontent (str): the contents to be set to

        Outputs:
        - (Block): the Block instance
    '''

    self.bcontent = str(bcontent)
    return self
def setMetadata(self, key, val)

Sets a custom metadata value

Metadata should not store block-specific data structures.

Inputs: - key (str): the key - val: the value (type is irrelevant)

Outputs: - (Block): the Block instance

Source code
def setMetadata(self, key, val):
    '''
        Sets a custom metadata value

        Metadata should not store block-specific data structures.

        Inputs:
        - key (str): the key
        - val: the value (type is irrelevant)

        Outputs:
        - (Block): the Block instance
    '''

    self.bmetadata[key] = val
    return self
def setType(self, btype)

Sets the type of the block

Inputs: - btype (str): the type of block to be set to

Outputs: - (Block): the Block instance

Source code
def setType(self, btype):
    '''
        Sets the type of the block

        Inputs:
        - btype (str): the type of block to be set to

        Outputs:
        - (Block): the Block instance
    '''

    self.btype = btype
    return self
def update(self, data=None, file=None)

Loads data from a block in to the current object.

Inputs: - data (str): - if None: will load from file by hash - else: will load from data string - file (str): - if None: will load from file specified in this parameter - else: will load from wherever block is stored by hash

Outputs: - (bool): indicates whether or not the operation was successful

Source code
def update(self, data = None, file = None):
    '''
        Loads data from a block in to the current object.

        Inputs:
        - data (str):
          - if None: will load from file by hash
          - else: will load from `data` string
        - file (str):
          - if None: will load from file specified in this parameter
          - else: will load from wherever block is stored by hash

        Outputs:
        - (bool): indicates whether or not the operation was successful
    '''
    try:
        # import from string
        blockdata = data

        # import from file
        if blockdata is None:
            try:
                blockdata = onionrstorage.getData(self.getHash())#.decode()
            except AttributeError:
                raise onionrexceptions.NoDataAvailable('Block does not exist')
        else:
            self.blockFile = None
        # parse block
        self.raw = blockdata
        self.bheader = json.loads(self.getRaw()[:self.getRaw().index(b'\n')])
        self.bcontent = self.getRaw()[self.getRaw().index(b'\n') + 1:]
        if ('encryptType' in self.bheader) and (self.bheader['encryptType'] in ('asym', 'sym')):
            self.bmetadata = self.getHeader('meta', None)
            self.isEncrypted = True
        else:
            self.bmetadata = json.loads(self.getHeader('meta', None))
        self.btype = self.getMetadata('type', None)
        self.signed = ('sig' in self.getHeader() and self.getHeader('sig') != '')
        # TODO: detect if signer is hash of pubkey or not
        self.signer = self.getHeader('signer', None)
        self.signature = self.getHeader('sig', None)
        # signed data is jsonMeta + block content (no linebreak)
        self.signedData = (None if not self.isSigned() else self.getHeader('meta') + self.getContent())
        self.date = blockmetadb.get_block_date(self.getHash())
        self.claimedTime = self.getHeader('time', None)

        if not self.getDate() is None:
            self.date = datetime.datetime.fromtimestamp(self.getDate())

        self.valid = True
        
        if self.autoDecrypt:
            self.decrypt()

        return True
    except Exception as e:
        logger.warn('Failed to parse block %s' % self.getHash(), error = e, timestamp = False)

        # if block can't be parsed, it's a waste of precious space. Throw it away.
        if not self.delete():
            logger.warn('Failed to delete invalid block %s.' % self.getHash(), error = e)
        else:
            logger.debug('Deleted invalid block %s.' % self.getHash(), timestamp = False)

    self.valid = False
    return False
def verifySig(self)

Verify if a block's signature is signed by its claimed signer

Source code
def verifySig(self):
    '''
        Verify if a block's signature is signed by its claimed signer
    '''
    if self.signer is None or signing.ed_verify(data=self.signedData, key=self.signer, sig=self.signature, encodedData=True):
        self.validSig = True
    else:
        self.validSig = False
    return self.validSig