account.py (4717B)
1 # standard imports 2 import os 3 4 # local imports 5 from .tag import Tag 6 from .crypto import Salter 7 8 9 class Account(Salter): 10 """Represents a single account in the cache. 11 12 An account is a blockchain address associated with one or more tags. It provides methods to compare addresses, view tags, merge tags from two accounts, as well as serializing and deserializing for storage. 13 14 The provided chain_spec will be used to generate the salt to obfuscate the address in the cache. 15 16 :param chain_spec: The chain spec the address is valid for 17 :type chain_spec: chainlib.chain.ChainSpec 18 :param account: The account address 19 :type account: bytes 20 :param label: Human-readable label for account used for logging 21 :type label: str 22 :param tags: Tags to associate account with 23 :type tags: list of bytes 24 :param create_digest: If set to false, account obfuscation will be omitted 25 :type create_digest: boolean 26 """ 27 def __init__(self, chain_spec, account, label=None, tags=[], create_digest=True): 28 super(Account, self).__init__(chain_spec) 29 30 if label == None: 31 label = str(account) 32 self.label = label 33 self.account_src = None 34 self.create_digest = create_digest 35 if self.create_digest: 36 self.account_src = account 37 self.account = self.sprinkle(self.account_src) 38 else: 39 self.account = account 40 self.tags = Tag() 41 for tag in tags: 42 self.tags.create(tag) 43 44 45 def tag(self, value): 46 """Add a tag to the account. 47 48 :param value: Literal tag value 49 :type value: bytes 50 """ 51 self.tags.create(value) 52 53 54 def sum(self): 55 """Get the sum of all the tags for the account. 56 57 :rtype: bytes 58 :returns: Tag sum 59 """ 60 return self.tags.get() 61 62 63 def connect(self, account): 64 """Associate two accounts with each other. After this operation, both accounts will have the same tag sum. 65 66 :param account: Account to merge with 67 :type account: taint.account.Account 68 """ 69 if not isinstance(account, Account): 70 raise TypeError('account must be type taint.account.Account') 71 self.tags.merge(account.tags) 72 73 74 def is_same(self, account): 75 """Compare two accounts. 76 77 This will not compare the tag state of the accounts. 78 79 :param account: Account to compare 80 :type account: taint.account.Account 81 :rtype: boolean 82 :return: True if the account effectively represents the same underlying blockchain address 83 """ 84 if not isinstance(account, Account): 85 raise TypeError('account must be type crypto_account_cache.account.Account') 86 return self.account == account.account 87 88 89 # def __eq__(self, account): 90 # return self.is_same(account) 91 92 93 def is_account(self, address): 94 """Compare blockchain address to address represented by account object. 95 96 If account obfuscation is being used, the input value has to match the unobfuscated value. 97 98 :param address: Address to compare with 99 :type address: bytes 100 :rtype: boolean 101 :return: True on address match 102 """ 103 if self.create_digest: 104 return self.sprinkle(address) == self.account 105 return address == self.account 106 107 108 def serialize(self): 109 """Serialize account object for storage. 110 111 Account serialization consists of serialization of the account's tags, followed by the serialization of the underlying blockchain address. 112 113 :rtype: bytes 114 :return: Serialized data 115 """ 116 b = self.tags.serialize() + self.account 117 return b 118 119 120 @staticmethod 121 def from_serialized(b, chain_spec, label=None): 122 """Deserialize account object from storage. 123 124 BUG: deserialization may break if account is not obfuscated, since the address may not end on 32 byte boundary 125 126 :param chain_spec: Chain spec to instantiate account for 127 :type chain_spec: chainlib.chain.ChainSpec 128 :param label: Human-readable label for logging 129 :type label: str 130 :rtype: taint.account.Account 131 :returns: Deserialized account 132 """ 133 l = len(b) 134 if l % 32 > 0: 135 raise ValueError('invalid data length; remainder {} of 32'.format(l % 32)) 136 if l < 64: 137 raise ValueError('invalid data length; expected minimum 64, got {}'.format(l)) 138 139 a = Account(chain_spec, b[-32:], label=label, create_digest=False) 140 a.tags.deserialize(b[:-32]) 141 return a 142 143 144 def __str__(self): 145 return '{} [{}]'.format(self.account.hex(), str(self.tags))