def __init__(self, coins, target):
        from containers import sumCurrencies

        self.coins = coins
        self.amount = sumCurrencies(coins)
        self.target = target

        import base64
        from crypto import _r as Random
        self.transaction_id = Random.getRandomString(128)
        self.encoded_transaction_id = base64.b64encode(self.transaction_id)

        Protocol.__init__(self)
        self.state = self.initiateHandshake
    def __init__(self,coins,target):
        from containers import sumCurrencies

        self.coins = coins
        self.amount = sumCurrencies(coins)
        self.target = target
        
        import base64
        from crypto import _r as Random
        self.transaction_id = Random.getRandomString(128)
        self.encoded_transaction_id = base64.b64encode(self.transaction_id)

        Protocol.__init__(self)
        self.state = self.initiateHandshake
    def handleCoins(self,message):
        import base64

        if message.type == 'TOKEN_SPEND':
            try:
                encoded_transaction_id, tokens, target = message.data
            except ValueError:
                return ProtocolErrorMessage('TS')

            if not isinstance(encoded_transaction_id, types.StringType):
                return ProtocolErrorMessage('TS')

            
            if not isinstance(tokens, types.ListType):
                return ProtocolErrorMessage('TS')
            
            if not tokens: # We require tokens
                return ProtocolErrorMessage('TS')
            for token in tokens:
                if not isinstance(token, types.ListType):
                    return ProtocolErrorMessage('TS')

            # Convert transaction_id
            try:
                transaction_id = base64.b64decode(encoded_transaction_id)
            except TypeError:
                return ProtocolErrorMessage('TS')

            # Convert the tokens
            try:
                tokens = [containers.CurrencyCoin().fromPython(c) for c in tokens]
            except AttributeError: #FIXME: this is the wrong error, I'm pretty sure
                return ProtocolErrorMessage('TS')

            
            # And now do things
            from containers import sumCurrencies

            #be conservative
            result = Message('TOKEN_REJECT','default')
            
            if transaction_id != self.transaction_id:
                #FIXME: is the a PROTOCOL_ERROR?
                result = Message('TOKEN_REJECT','Rejected')

            elif sumCurrencies(tokens) != self.sum:
                result = Message('TOKEN_REJECT','Rejected')
            
            elif target != self.target:
                result = Message('TOKEN_REJECT','Rejected')
            
            elif self.action in ['redeem','exchange','trust']:
                out = self.wallet.handleIncomingCoins(tokens,self.action,target)
                if out:
                    result = Message('TOKEN_ACCEPT')

            self.state = self.goodbye
            return result

        elif message.type == 'PROTOCOL_ERROR':
            pass

        else:
            return ProtocolErrorMessage('TokenSendRecipient')
                        # send the key to the mint.
                        failures.append(mintKey.encodeField('key_identifier'))

                if failures:
                    self.issuer.dsdb.unlock(transaction_id)
                    return Message('TRANSFER_TOKEN_REJECT', [encoded_transaction_id, 'Blind', 'Invalid key_identifier', []])

                # Make sure that we have the same amount of coins as mintings
                from fraction import Fraction
                from containers import sumCurrencies

                total = Fraction('0')
                for b in blinds:
                    total += b[0].denomination * len(b[1])

                if total != sumCurrencies(coins):
                    self.issuer.dsdb.unlock(transaction_id)
                    return Message('TRANSFER_TOKEN_REJECT', [encoded_transaction_id, 'Generic', 'Rejected', []])

                # mint them immediately (the only thing we can do right now with the mint)
                minted = []
                from entities import MintError
                for key, blindlist in blinds:
                    this_set = []
                    for blind in blindlist:
                        try:
                            signature = self.issuer.mint.signNow(key.key_identifier, blind)
                        except MintError:
                            self.issuer.dsdb.unlock(transaction_id)
                            return Message('TRANSFER_TOKEN_REJECT', [encoded_transaction_id, 'Blind', 'Unable to sign', []])
                        this_set.append(base64.b64encode(signature))
    def sendCoins(self,transport,target,amount=None):
        """sendCoins sends coins over a transport to a target of a certain amount.
        
        We need to be careful to try every possible working combination of coins to
        to an amount. To test, we muck in the internals of the coin sending to make
        sure that we get the right coins. (Note: This would be easier if we just
        had a function to get the coins for an amount.)

        To test the functionality we steal the transport, and when a message occurs,
        we steal the tokens directly out of the protocol. This is highly dependant
        on the internal details of TokenSpendSender and the transport/protocol
        relationship.

        >>> class transport:
        ...     from containers import sumCurrencies
        ...     def setProtocol(self, protocol): 
        ...         protocol.transport = self
        ...         self.protocol = protocol
        ...     def start(self): pass
        ...     def write(self, info): print sumCurrencies(self.protocol.coins) # Steal the values 
        
        >>> wallet = Wallet()
        >>> test = lambda x: wallet.sendCoins(transport(), '', x)

        >>> from tests import coins
        >>> from containers import sumCurrencies
        >>> from fraction import Fraction

        Okay. Do some simple checks to make sure things work at all
        
        >>> wallet.coins = [coins[0][0]]
        >>> test(Fraction(1))
        1

        >>> wallet.coins = [coins[0][0], coins[2][0]]
        >>> test(Fraction(6))
        6

        >>> wallet.coins = [coins[2][0], coins[0][0]]
        >>> test(Fraction(6))
        6

        Okay. Now we'll do some more advanced tests of the system. We start off with
        a specifically selected group of coins:
        3 coins of denomination 2
        1 coin of denomination 5
        1 coin of denomination 10
        >>> test_coins = [coins[1][0], coins[1][1], coins[1][2], coins[2][0], coins[3][0]]
        
        >>> test_coins[0].denomination == test_coins[1].denomination == test_coins[2].denomination
        True
        >>> test_coins[0].denomination
        <Fraction: 2>
        >>> test_coins[3].denomination
        <Fraction: 5>
        >>> test_coins[4].denomination
        <Fraction: 10>

        >>> sumCurrencies(test_coins)
        <Fraction: 21>

        Now, this group of coins has some specific properties. There are only certain ways to
        get certain values of coins. We'll be testing 6, 11, 15, 16, 19, and 21

        6 = 2 + 2 + 2
        >>> wallet.coins = test_coins
        >>> sumCurrencies(wallet.coins)
        <Fraction: 21>
        >>> test(Fraction(6))
        6

        11 = 5 + 2 + 2 + 2
        >>> wallet.coins = test_coins
        >>> test(Fraction(11))
        11

        15 = 10 + 5
        >>> wallet.coins = test_coins
        >>> test(Fraction(15))
        15

        16 = 10 + 2 + 2 + 2
        >>> wallet.coins = test_coins
        >>> test(Fraction(16))
        16

        19 = 10 + 5 + 2 + 2
        >>> wallet.coins = test_coins
        >>> test(Fraction(19))
        19

        21 = 10 + 5 + 2 + 2 + 2
        >>> wallet.coins = [coins[1][0], coins[1][1], coins[1][2], coins[2][0], coins[3][0]]
        >>> test(Fraction(21))
        21

        Okay. Some things we can't do.

        8 = Impossible
        >>> wallet.coins = test_coins
        >>> test(Fraction(8))
        Traceback (most recent call last):
        UnableToDoError: Not enough tokens

        22 = Impossible
        >>> wallet.coins = test_coins
        >>> test(Fraction(22))
        Traceback (most recent call last):
        UnableToDoError: Not enough tokens

        Okay. Now we want to make sure we don't lose coins if there is an exception
        that occurs.

        >>> test = lambda x: wallet.sendCoins('foo', '', x)
        >>> wallet.coins = test_coins
        >>> test(Fraction(21))
        Traceback (most recent call last):
        AttributeError: 'str' object has no attribute ...

        >>> wallet.coins == test_coins
        True

        """
        from containers import sumCurrencies
        from fraction import Fraction

        if not self.coins:
            raise UnableToDoError('Not enough tokens')

        if sumCurrencies(self.coins) < amount:
            raise UnableToDoError('Not enough tokens')

        denominations = {} # A dictionary of coins by denomination
        denomination_list = [] # A list of the denomination of every coin
        for coin in self.coins:
            denominations.setdefault(coin.denomination, [])
            denominations[coin.denomination].append(coin)

            denomination_list.append(coin.denomination)
            

        denomination_list.sort(reverse=True) # sort from high to low

        def my_split(piece_list, sum):
            # piece_list must be sorted from high to low
            
            # Delete all coins greater than sum
            my_list = [p for p in piece_list if p <= sum]

            while my_list:
                test_piece = my_list[0]
                del my_list[0]

                if test_piece == sum: 
                    return [test_piece]

                test_partition = my_split(my_list, sum - test_piece)

                if test_partition == [] :
                    # Partitioning the rest failed, so remove all pieces of this size
                    my_list = [p for p in my_list if p < test_piece]
                else :
                    test_partition.append(test_piece)
                    return test_partition

            # if we are here, we don't have a set of coins that works
            return []

        if reduce(Fraction.__add__, denomination_list, Fraction(0)) != sumCurrencies(self.coins):
            raise Exception('denomination_list and self.coins differ!')

        denominations_to_use = my_split(denomination_list, amount)

        if not denominations_to_use:
            raise UnableToDoError('Not enough tokens')

        denominations_to_use = [str(d) for d in denominations_to_use]

        to_use = []
        for denomination in denominations_to_use:
            to_use.append(denominations[denomination].pop()) # Make sure we remove the coins from denominations!

        for coin in to_use: # Remove the coins to prevent accidental double spending
            self.coins.remove(coin)

        try:
            protocol = protocols.TokenSpendSender(to_use,target)
            transport.setProtocol(protocol)
            transport.start()
            protocol.newMessage(Message(None))
        except: # Catch everything. Losing coins is death. We re-raise anyways.
            # If we had an error at the protocol or transport layer, make sure we don't lose the coins
            self.coins.extend(to_use)
            raise

        if protocol.state != protocol.finish:
            # If we didn't succeed, re-add the coins to the wallet.
            # Of course, we may need to remint, so FIXME: look at this
            self.coins.extend(to_use)
    def handleCoins(self, message):
        import base64

        if message.type == 'TOKEN_SPEND':
            try:
                encoded_transaction_id, tokens, target = message.data
            except ValueError:
                return ProtocolErrorMessage('TS')

            if not isinstance(encoded_transaction_id, types.StringType):
                return ProtocolErrorMessage('TS')

            if not isinstance(tokens, types.ListType):
                return ProtocolErrorMessage('TS')

            if not tokens:  # We require tokens
                return ProtocolErrorMessage('TS')
            for token in tokens:
                if not isinstance(token, types.ListType):
                    return ProtocolErrorMessage('TS')

            # Convert transaction_id
            try:
                transaction_id = base64.b64decode(encoded_transaction_id)
            except TypeError:
                return ProtocolErrorMessage('TS')

            # Convert the tokens
            try:
                tokens = [
                    containers.CurrencyCoin().fromPython(c) for c in tokens
                ]
            except AttributeError:  #FIXME: this is the wrong error, I'm pretty sure
                return ProtocolErrorMessage('TS')

            # And now do things
            from containers import sumCurrencies

            #be conservative
            result = Message('TOKEN_REJECT', 'default')

            if transaction_id != self.transaction_id:
                #FIXME: is the a PROTOCOL_ERROR?
                result = Message('TOKEN_REJECT', 'Rejected')

            elif sumCurrencies(tokens) != self.sum:
                result = Message('TOKEN_REJECT', 'Rejected')

            elif target != self.target:
                result = Message('TOKEN_REJECT', 'Rejected')

            elif self.action in ['redeem', 'exchange', 'trust']:
                out = self.wallet.handleIncomingCoins(tokens, self.action,
                                                      target)
                if out:
                    result = Message('TOKEN_ACCEPT')

            self.state = self.goodbye
            return result

        elif message.type == 'PROTOCOL_ERROR':
            pass

        else:
            return ProtocolErrorMessage('TokenSendRecipient')
                if failures:
                    self.issuer.dsdb.unlock(transaction_id)
                    return Message('TRANSFER_TOKEN_REJECT', [
                        encoded_transaction_id, 'Blind',
                        'Invalid key_identifier', []
                    ])

                # Make sure that we have the same amount of coins as mintings
                from fraction import Fraction
                from containers import sumCurrencies

                total = Fraction('0')
                for b in blinds:
                    total += b[0].denomination * len(b[1])

                if total != sumCurrencies(coins):
                    self.issuer.dsdb.unlock(transaction_id)
                    return Message(
                        'TRANSFER_TOKEN_REJECT',
                        [encoded_transaction_id, 'Generic', 'Rejected', []])

                # mint them immediately (the only thing we can do right now with the mint)
                minted = []
                from entities import MintError
                for key, blindlist in blinds:
                    this_set = []
                    for blind in blindlist:
                        try:
                            signature = self.issuer.mint.signNow(
                                key.key_identifier, blind)
                        except MintError:
Example #8
0
    def sendCoins(self, transport, target, amount=None):
        """sendCoins sends coins over a transport to a target of a certain amount.
        
        We need to be careful to try every possible working combination of coins to
        to an amount. To test, we muck in the internals of the coin sending to make
        sure that we get the right coins. (Note: This would be easier if we just
        had a function to get the coins for an amount.)

        To test the functionality we steal the transport, and when a message occurs,
        we steal the tokens directly out of the protocol. This is highly dependant
        on the internal details of TokenSpendSender and the transport/protocol
        relationship.

        >>> class transport:
        ...     from containers import sumCurrencies
        ...     def setProtocol(self, protocol): 
        ...         protocol.transport = self
        ...         self.protocol = protocol
        ...     def start(self): pass
        ...     def write(self, info): print sumCurrencies(self.protocol.coins) # Steal the values 
        
        >>> wallet = Wallet()
        >>> test = lambda x: wallet.sendCoins(transport(), '', x)

        >>> from tests import coins
        >>> from containers import sumCurrencies
        >>> from fraction import Fraction

        Okay. Do some simple checks to make sure things work at all
        
        >>> wallet.coins = [coins[0][0]]
        >>> test(Fraction(1))
        1

        >>> wallet.coins = [coins[0][0], coins[2][0]]
        >>> test(Fraction(6))
        6

        >>> wallet.coins = [coins[2][0], coins[0][0]]
        >>> test(Fraction(6))
        6

        Okay. Now we'll do some more advanced tests of the system. We start off with
        a specifically selected group of coins:
        3 coins of denomination 2
        1 coin of denomination 5
        1 coin of denomination 10
        >>> test_coins = [coins[1][0], coins[1][1], coins[1][2], coins[2][0], coins[3][0]]
        
        >>> test_coins[0].denomination == test_coins[1].denomination == test_coins[2].denomination
        True
        >>> test_coins[0].denomination
        <Fraction: 2>
        >>> test_coins[3].denomination
        <Fraction: 5>
        >>> test_coins[4].denomination
        <Fraction: 10>

        >>> sumCurrencies(test_coins)
        <Fraction: 21>

        Now, this group of coins has some specific properties. There are only certain ways to
        get certain values of coins. We'll be testing 6, 11, 15, 16, 19, and 21

        6 = 2 + 2 + 2
        >>> wallet.coins = test_coins
        >>> sumCurrencies(wallet.coins)
        <Fraction: 21>
        >>> test(Fraction(6))
        6

        11 = 5 + 2 + 2 + 2
        >>> wallet.coins = test_coins
        >>> test(Fraction(11))
        11

        15 = 10 + 5
        >>> wallet.coins = test_coins
        >>> test(Fraction(15))
        15

        16 = 10 + 2 + 2 + 2
        >>> wallet.coins = test_coins
        >>> test(Fraction(16))
        16

        19 = 10 + 5 + 2 + 2
        >>> wallet.coins = test_coins
        >>> test(Fraction(19))
        19

        21 = 10 + 5 + 2 + 2 + 2
        >>> wallet.coins = [coins[1][0], coins[1][1], coins[1][2], coins[2][0], coins[3][0]]
        >>> test(Fraction(21))
        21

        Okay. Some things we can't do.

        8 = Impossible
        >>> wallet.coins = test_coins
        >>> test(Fraction(8))
        Traceback (most recent call last):
        UnableToDoError: Not enough tokens

        22 = Impossible
        >>> wallet.coins = test_coins
        >>> test(Fraction(22))
        Traceback (most recent call last):
        UnableToDoError: Not enough tokens

        Okay. Now we want to make sure we don't lose coins if there is an exception
        that occurs.

        >>> test = lambda x: wallet.sendCoins('foo', '', x)
        >>> wallet.coins = test_coins
        >>> test(Fraction(21))
        Traceback (most recent call last):
        AttributeError: 'str' object has no attribute ...

        >>> wallet.coins == test_coins
        True

        """
        from containers import sumCurrencies
        from fraction import Fraction

        if not self.coins:
            raise UnableToDoError('Not enough tokens')

        if sumCurrencies(self.coins) < amount:
            raise UnableToDoError('Not enough tokens')

        denominations = {}  # A dictionary of coins by denomination
        denomination_list = []  # A list of the denomination of every coin
        for coin in self.coins:
            denominations.setdefault(coin.denomination, [])
            denominations[coin.denomination].append(coin)

            denomination_list.append(coin.denomination)

        denomination_list.sort(reverse=True)  # sort from high to low

        def my_split(piece_list, sum):
            # piece_list must be sorted from high to low

            # Delete all coins greater than sum
            my_list = [p for p in piece_list if p <= sum]

            while my_list:
                test_piece = my_list[0]
                del my_list[0]

                if test_piece == sum:
                    return [test_piece]

                test_partition = my_split(my_list, sum - test_piece)

                if test_partition == []:
                    # Partitioning the rest failed, so remove all pieces of this size
                    my_list = [p for p in my_list if p < test_piece]
                else:
                    test_partition.append(test_piece)
                    return test_partition

            # if we are here, we don't have a set of coins that works
            return []

        if reduce(Fraction.__add__, denomination_list,
                  Fraction(0)) != sumCurrencies(self.coins):
            raise Exception('denomination_list and self.coins differ!')

        denominations_to_use = my_split(denomination_list, amount)

        if not denominations_to_use:
            raise UnableToDoError('Not enough tokens')

        denominations_to_use = [str(d) for d in denominations_to_use]

        to_use = []
        for denomination in denominations_to_use:
            to_use.append(denominations[denomination].pop(
            ))  # Make sure we remove the coins from denominations!

        for coin in to_use:  # Remove the coins to prevent accidental double spending
            self.coins.remove(coin)

        try:
            protocol = protocols.TokenSpendSender(to_use, target)
            transport.setProtocol(protocol)
            transport.start()
            protocol.newMessage(Message(None))
        except:  # Catch everything. Losing coins is death. We re-raise anyways.
            # If we had an error at the protocol or transport layer, make sure we don't lose the coins
            self.coins.extend(to_use)
            raise

        if protocol.state != protocol.finish:
            # If we didn't succeed, re-add the coins to the wallet.
            # Of course, we may need to remint, so FIXME: look at this
            self.coins.extend(to_use)