Example #1
0
    def __init__(self, log, name, wallet, blockchain):
        super(WalletAccount, self).__init__()
        self.name = name
        self.wallet = wallet
        self.blockchain = blockchain
        self.blockchain.subscribe(blockchain.EVT_NEW_HIGHEST_BLOCK,
                                  self.on_new_highest_block)
        self.log = log
        self.lastblock_time = 0
        self.last_tx_publish = {}
        self.confirmed_outputs = []
        self.unconfirmed_outputs = []
        self.confirmed_transactions = {}
        self.unconfirmed_transactions = {}

        #Satoshi wallet format doesn't store confirmations so we have
        #to recompute confirmations every time.
        self.blockchain_height = self.blockchain.get_height()
        self.compute_balances()

        self.coin_selector = CoinSelector()
        self.schedule_republish_transactions()
Example #2
0
    def __init__(self, log, name, wallet, blockchain):
        super(WalletAccount, self).__init__()
        self.name = name
        self.wallet = wallet
        self.blockchain = blockchain
        self.blockchain.subscribe(blockchain.EVT_NEW_HIGHEST_BLOCK, self.on_new_highest_block)
        self.log = log
        self.lastblock_time = 0
        self.last_tx_publish = {}
        self.confirmed_outputs = []
        self.unconfirmed_outputs = []
        self.confirmed_transactions = {}
        self.unconfirmed_transactions = {}

        # Satoshi wallet format doesn't store confirmations so we have
        # to recompute confirmations every time.
        self.blockchain_height = self.blockchain.get_height()
        self.compute_balances()

        self.coin_selector = CoinSelector()
        self.schedule_republish_transactions()
    from coinpy.lib.wallet.wallet import Wallet
    from coinpy.lib.wallet.bsddb.bsddb_wallet_database import BSDDBWalletDatabase
    from coinpy.lib.wallet.coin_selector import CoinSelector
    from coinpy.tools.bitcoin.base58check import decode_base58check
    from coinpy.model.constants.bitcoin import COIN
    from coinpy.lib.vm.vm import TxValidationVM

    import os

    runmode = TESTNET
    wallet_filename = "D:\\repositories\\coinpy\\coinpy-client\\src\\data\\testnet\\wallet_testnet.dat"
    directory, filename = os.path.split(wallet_filename)
    bsddb_env = BSDDBEnv(directory)
    wallet = Wallet(None, BSDDBWalletDatabase(bsddb_env, filename), runmode)

    selector = CoinSelector()
    amount, fee = 145.0065 * COIN, 0 * COIN
    #list of (txhash, tx, index, txout)
    outputs = list(wallet.iter_my_outputs())
    selected_outputs = selector.select_coins(outputs, amount + fee)
    tx = create_pubkeyhash_transaction(
        selected_outputs,
        decode_base58check("n4MsBRWD7VxKGsqYRSLaFZC6hQrsrKLaZo"),
        decode_base58check("n4MsBRWD7VxKGsqYRSLaFZC6hQrsrKLaZo"), amount, fee)
    sign_transaction(tx, selected_outputs)
    vm = TxValidationVM()
    print tx
    print len(selected_outputs)
    print vm.validate(tx, 0, selected_outputs[0].txout.script,
                      tx.in_list[0].script)
    print vm.validate(tx, 1, selected_outputs[1].txout.script,
Example #4
0
class WalletAccount(Observable):
    EVT_BALANCE_CHANGED = Observable.createevent()
    EVT_PUBLISH_TRANSACTION = Observable.createevent()
    EVT_NEW_TRANSACTION_ITEM = Observable.createevent()
    EVT_CONFIRMED_TRANSACTION_ITEM = Observable.createevent()
    EVT_NEW_ADDRESS_DESCRIPTION = Observable.createevent()

    def __init__(self, log, name, wallet, blockchain):
        super(WalletAccount, self).__init__()
        self.name = name
        self.wallet = wallet
        self.blockchain = blockchain
        self.blockchain.subscribe(blockchain.EVT_NEW_HIGHEST_BLOCK, self.on_new_highest_block)
        self.log = log
        self.lastblock_time = 0
        self.last_tx_publish = {}
        self.confirmed_outputs = []
        self.unconfirmed_outputs = []
        self.confirmed_transactions = {}
        self.unconfirmed_transactions = {}

        # Satoshi wallet format doesn't store confirmations so we have
        # to recompute confirmations every time.
        self.blockchain_height = self.blockchain.get_height()
        self.compute_balances()

        self.coin_selector = CoinSelector()
        self.schedule_republish_transactions()

    """
     
    Set the followings attributes:
        - status_unknown : True if no BlockLocator() was found in the wallet.
        - is_blockchain_synched : only valid if not status_unknown, True if the blockchain is height is greater than the wallet height.
        - confirmed_outputs : 
        - unconfirmed_outputs : 
        - confirmed_transactions : 
        - unconfirmed_transactions :
    """

    def compute_balances(self):
        # improvement: could only check if all my transactions are present in the blockchain
        wallet_besthash = self.wallet.get_besthash_reference()
        self.status_unknown = not wallet_besthash
        if not self.status_unknown:
            self.is_blockchain_synched = self.blockchain.contains_block(wallet_besthash)

        # fillin confirmed/unconfirmed outputs (allows to compute the balance)
        self.confirmed_outputs = []
        self.unconfirmed_outputs = []
        for tx, outpoint, txout in self.wallet.iter_my_outputs():
            confirmed = False
            if (
                self.blockchain.contains_transaction(outpoint.hash)
                and self.blockchain.get_transaction_handle(outpoint.hash).get_block().is_mainchain()
            ):
                height = self.blockchain.get_transaction_handle(outpoint.hash).get_block().get_height()
                confirmed = self.is_confirmed(tx, height)
            if confirmed:
                self.confirmed_outputs.append([tx, txout])
            else:
                self.unconfirmed_outputs.append([tx, txout])
        confirmed_transactions = {}
        unconfirmed_transactions = {}
        # compute the balance
        self.unconfirmed_balance = sum(txout.value for tx, txout in self.unconfirmed_outputs)
        self.confirmed_balance = sum(txout.value for tx, txout in self.confirmed_outputs)
        self.fire(
            self.EVT_BALANCE_CHANGED,
            confirmed=self.confirmed_balance,
            unconfirmed=self.unconfirmed_balance,
            height=self.blockchain_height,
        )

        # fillin confirmed/unconfirmed outputs transactions (for history and confirmations)
        for wallet_tx, hash, date, address, name, amount in self.wallet.iter_transaction_history():
            confirmed = False
            if self.blockchain.contains_transaction(hash):
                height = self.blockchain.get_transaction_handle(hash).get_block().get_height()
                confirmed = self.is_confirmed(wallet_tx.merkle_tx.tx, height)
            if confirmed:
                confirmed_transactions[hash] = [wallet_tx, hash, date, address, name, amount]
            else:
                unconfirmed_transactions[hash] = [wallet_tx, hash, date, address, name, amount]
                self.last_tx_publish[hash] = 0
        # send newly confirmed transaction events
        for txhash in self.unconfirmed_transactions:
            if txhash in confirmed_transactions:
                self.fire(self.EVT_CONFIRMED_TRANSACTION_ITEM, item=(txhash,))
        self.confirmed_transactions = confirmed_transactions
        self.unconfirmed_transactions = unconfirmed_transactions

    def iter_my_outputs(self):
        return self.wallet.iter_my_outputs()

    def iter_transaction_history(self):
        for wallet_tx, hash, date, address, name, amount in self.confirmed_transactions.values():
            yield (wallet_tx, hash, date, address, name, amount, True)
        for wallet_tx, hash, date, address, name, amount in self.unconfirmed_transactions.values():
            yield (wallet_tx, hash, date, address, name, amount, False)

    """Return the confirmed account balance.
    
     Return the received coins seen in mainchain at a depth of minimum 6
     and the minted coins in mainchain at a depth of COINBASE_MATURITY.
    """

    def get_confirmed_balance(self):
        return self.confirmed_balance

    """Return the unconfirmed account balance.
     
    Return the coins not seen in mainchain, the coins seen in mainchain
    with a depth < 6, and the coins minted whith a depth < COINBASE_MATURITY.
    """

    def get_unconfirmed_balance(self):
        return self.unconfirmed_balance

    def get_blockchain_height(self):
        return self.blockchain_height

    def is_confirmed(self, tx, height):
        if tx.iscoinbase():
            return self.blockchain_height > height + COINBASE_MATURITY
        return self.blockchain_height > height + CONFIRMATIONS

    def on_new_highest_block(self, event):
        self.blockchain_height = event.height
        self.lastblock_time = time.time()
        self.compute_balances()

    def get_besthash(self):
        return self.wallet.get_besthash_reference()

    """
         Do this infrequently and randomly to avoid giving away
         that these are our transactions.
    """

    def republish_transactions(self):
        tnow = time.time()
        for wallet_tx, txhash, date, address, name, amount in self.unconfirmed_transactions.values():
            # Only if at least one block was received more than 5min after last publishing.
            if self.lastblock_time > self.last_tx_publish[txhash] + 5 * 60:
                self.fire(self.EVT_PUBLISH_TRANSACTION, txhash=txhash, tx=wallet_tx.merkle_tx.tx)
                self.last_tx_publish[hash] = tnow
        self.schedule_republish_transactions()

    """"""

    def schedule_republish_transactions(self):
        seconds = random.randint(5 * 60, 30 * 60)
        reactor.call_later(seconds, self.republish_transactions)

    def get_receive_address(self):
        public_key = self.wallet.get_receive_key()
        return BitcoinAddress.from_publickey(public_key, self.wallet.runmode)

    def set_receive_label(self, address_base58, label):
        address = BitcoinAddress.from_base58addr(address_base58)
        self.wallet.begin_updates()
        public_key, is_crypted = self.wallet.addresses[address]  # decode_base58check(address)[1:]
        self.wallet.allocate_key(public_key, label)
        self.wallet.commit_updates()

        new_description = self.wallet.get_address_description(public_key)
        self.fire(self.EVT_NEW_ADDRESS_DESCRIPTION, public_key=public_key, description=new_description)

    """
    
        amount: value in COIN.
    """

    def create_transaction(self, amount, address, fee):
        outputs = [(outpoint, txout) for (tx, outpoint, txout) in self.iter_my_outputs()]
        selected_outputs = self.coin_selector.select_coins(outputs, (amount + fee))

        change_public_key = self.wallet.get_receive_key()
        change_address = BitcoinAddress.from_publickey(change_public_key, self.wallet.runmode)
        tx = create_pubkeyhash_transaction(
            selected_outputs,
            address.get_hash160(),  # decode_base58check(address)[1:],  #remove ADDRESSVERSION[runmode] byte
            change_address.get_hash160(),  # decode_base58check(change_address)[1:],  #remove ADDRESSVERSION[runmode] byte
            amount,
            fee,
        )
        return PlannedTransaction(selected_outputs, amount, address, change_public_key, change_address, fee, tx)

    def is_passphrase_required(self, planned_transaction):
        for outpoint, txout in planned_transaction.selected_outputs:
            if self.wallet.is_passphrase_required(txout):
                return True
        return False

    def send_transaction(self, planned_tx, passphrases):
        try:
            self.wallet.unlock(passphrases)
            privkey_list = []
            for outpoint, txout in planned_tx.selected_outputs:
                privkey_list.append(self.wallet.get_txout_private_key_secret(txout))
        finally:
            self.wallet.lock()
        sign_transaction(planned_tx.tx, [txout for outpoint, txout in planned_tx.selected_outputs], privkey_list)
        txhash = hash_tx(planned_tx.tx)
        self.log.info(
            "Sending %f to %s (fee:%f), change address: %s, hash:%s"
            % (planned_tx.amount, str(planned_tx.address), planned_tx.fee, str(planned_tx.change_address), str(txhash))
        )
        # Initially, create an empty MerkleTx (the tx is not yet in a block)
        merkle_tx = MerkleTx(planned_tx.tx, Uint256.zero(), [], 4294967295)
        self.wallet.begin_updates()
        self.wallet.allocate_key(planned_tx.change_public_key, ischange=True)
        # Set the spend flags for the input transactions
        for outpoint, txout in planned_tx.selected_outputs:
            input_wallet_tx = self.wallet.get_transaction(outpoint.hash)
            input_wallet_tx.set_spent(outpoint.index)
            self.wallet.set_transaction(outpoint.hash, input_wallet_tx)
        # Add the wallet_tx (contains supporting transations)
        txtime = int(time.time())
        wallet_tx = create_wallet_tx(self.blockchain, merkle_tx, txtime)
        self.wallet.add_transaction(txhash, wallet_tx)
        self.wallet.commit_updates()
        self.fire(
            self.EVT_NEW_TRANSACTION_ITEM,
            item=(planned_tx.tx, txhash, txtime, planned_tx.address, "", -planned_tx.amount, False),
        )

        self.compute_balances()  # we could only compute delta here
        self.fire(self.EVT_PUBLISH_TRANSACTION, txhash=txhash, tx=planned_tx.tx)
        self.last_tx_publish[txhash] = txtime
        # update description of change address
        new_description = self.wallet.get_address_description(planned_tx.change_public_key)
        self.fire(
            self.EVT_NEW_ADDRESS_DESCRIPTION, public_key=planned_tx.change_public_key, description=new_description
        )
Example #5
0
    from coinpy.lib.wallet.wallet import Wallet
    from coinpy.lib.wallet.bsddb.bsddb_wallet_database import BSDDBWalletDatabase
    from coinpy.lib.wallet.coin_selector import CoinSelector
    from coinpy.tools.bitcoin.base58check import decode_base58check
    from coinpy.model.constants.bitcoin import COIN
    from coinpy.lib.vm.vm import TxValidationVM

    import os

    runmode = TESTNET
    wallet_filename = "D:\\repositories\\coinpy\\coinpy-client\\src\\data\\testnet\\wallet_testnet.dat"
    directory, filename = os.path.split(wallet_filename)
    bsddb_env = BSDDBEnv(directory)
    wallet = Wallet(None, BSDDBWalletDatabase(bsddb_env, filename), runmode)

    selector = CoinSelector()
    amount, fee = 145.0065 * COIN, 0 * COIN
    # list of (txhash, tx, index, txout)
    outputs = list(wallet.iter_my_outputs())
    selected_outputs = selector.select_coins(outputs, amount + fee)
    tx = create_pubkeyhash_transaction(
        selected_outputs,
        decode_base58check("n4MsBRWD7VxKGsqYRSLaFZC6hQrsrKLaZo"),
        decode_base58check("n4MsBRWD7VxKGsqYRSLaFZC6hQrsrKLaZo"),
        amount,
        fee,
    )
    sign_transaction(tx, selected_outputs)
    vm = TxValidationVM()
    print tx
    print len(selected_outputs)
Example #6
0
class WalletAccount(Observable):
    EVT_BALANCE_CHANGED = Observable.createevent()
    EVT_PUBLISH_TRANSACTION = Observable.createevent()
    EVT_NEW_TRANSACTION_ITEM = Observable.createevent()
    EVT_CONFIRMED_TRANSACTION_ITEM = Observable.createevent()
    EVT_NEW_ADDRESS_DESCRIPTION = Observable.createevent()

    def __init__(self, log, name, wallet, blockchain):
        super(WalletAccount, self).__init__()
        self.name = name
        self.wallet = wallet
        self.blockchain = blockchain
        self.blockchain.subscribe(blockchain.EVT_NEW_HIGHEST_BLOCK,
                                  self.on_new_highest_block)
        self.log = log
        self.lastblock_time = 0
        self.last_tx_publish = {}
        self.confirmed_outputs = []
        self.unconfirmed_outputs = []
        self.confirmed_transactions = {}
        self.unconfirmed_transactions = {}

        #Satoshi wallet format doesn't store confirmations so we have
        #to recompute confirmations every time.
        self.blockchain_height = self.blockchain.get_height()
        self.compute_balances()

        self.coin_selector = CoinSelector()
        self.schedule_republish_transactions()

    '''
     
    Set the followings attributes:
        - status_unknown : True if no BlockLocator() was found in the wallet.
        - is_blockchain_synched : only valid if not status_unknown, True if the blockchain is height is greater than the wallet height.
        - confirmed_outputs : 
        - unconfirmed_outputs : 
        - confirmed_transactions : 
        - unconfirmed_transactions :
    '''

    def compute_balances(self):
        #improvement: could only check if all my transactions are present in the blockchain
        wallet_besthash = self.wallet.get_besthash_reference()
        self.status_unknown = not wallet_besthash
        if not self.status_unknown:
            self.is_blockchain_synched = self.blockchain.contains_block(
                wallet_besthash)

        #fillin confirmed/unconfirmed outputs (allows to compute the balance)
        self.confirmed_outputs = []
        self.unconfirmed_outputs = []
        for tx, outpoint, txout in self.wallet.iter_my_outputs():
            confirmed = False
            if (self.blockchain.contains_transaction(outpoint.hash)
                    and self.blockchain.get_transaction_handle(
                        outpoint.hash).get_block().is_mainchain()):
                height = self.blockchain.get_transaction_handle(
                    outpoint.hash).get_block().get_height()
                confirmed = self.is_confirmed(tx, height)
            if confirmed:
                self.confirmed_outputs.append([tx, txout])
            else:
                self.unconfirmed_outputs.append([tx, txout])
        confirmed_transactions = {}
        unconfirmed_transactions = {}
        #compute the balance
        self.unconfirmed_balance = sum(
            txout.value for tx, txout in self.unconfirmed_outputs)
        self.confirmed_balance = sum(txout.value
                                     for tx, txout in self.confirmed_outputs)
        self.fire(self.EVT_BALANCE_CHANGED,
                  confirmed=self.confirmed_balance,
                  unconfirmed=self.unconfirmed_balance,
                  height=self.blockchain_height)

        #fillin confirmed/unconfirmed outputs transactions (for history and confirmations)
        for wallet_tx, hash, date, address, name, amount in self.wallet.iter_transaction_history(
        ):
            confirmed = False
            if self.blockchain.contains_transaction(hash):
                height = self.blockchain.get_transaction_handle(
                    hash).get_block().get_height()
                confirmed = self.is_confirmed(wallet_tx.merkle_tx.tx, height)
            if (confirmed):
                confirmed_transactions[hash] = [
                    wallet_tx, hash, date, address, name, amount
                ]
            else:
                unconfirmed_transactions[hash] = [
                    wallet_tx, hash, date, address, name, amount
                ]
                self.last_tx_publish[hash] = 0
        #send newly confirmed transaction events
        for txhash in self.unconfirmed_transactions:
            if txhash in confirmed_transactions:
                self.fire(self.EVT_CONFIRMED_TRANSACTION_ITEM, item=(txhash, ))
        self.confirmed_transactions = confirmed_transactions
        self.unconfirmed_transactions = unconfirmed_transactions

    def iter_my_outputs(self):
        return self.wallet.iter_my_outputs()

    def iter_transaction_history(self):
        for wallet_tx, hash, date, address, name, amount in self.confirmed_transactions.values(
        ):
            yield (wallet_tx, hash, date, address, name, amount, True)
        for wallet_tx, hash, date, address, name, amount in self.unconfirmed_transactions.values(
        ):
            yield (wallet_tx, hash, date, address, name, amount, False)

    '''Return the confirmed account balance.
    
     Return the received coins seen in mainchain at a depth of minimum 6
     and the minted coins in mainchain at a depth of COINBASE_MATURITY.
    '''

    def get_confirmed_balance(self):
        return self.confirmed_balance

    '''Return the unconfirmed account balance.
     
    Return the coins not seen in mainchain, the coins seen in mainchain
    with a depth < 6, and the coins minted whith a depth < COINBASE_MATURITY.
    '''

    def get_unconfirmed_balance(self):
        return self.unconfirmed_balance

    def get_blockchain_height(self):
        return self.blockchain_height

    def is_confirmed(self, tx, height):
        if tx.iscoinbase():
            return (self.blockchain_height > height + COINBASE_MATURITY)
        return (self.blockchain_height > height + CONFIRMATIONS)

    def on_new_highest_block(self, event):
        self.blockchain_height = event.height
        self.lastblock_time = time.time()
        self.compute_balances()

    def get_besthash(self):
        return self.wallet.get_besthash_reference()

    """
         Do this infrequently and randomly to avoid giving away
         that these are our transactions.
    """

    def republish_transactions(self):
        tnow = time.time()
        for wallet_tx, txhash, date, address, name, amount in self.unconfirmed_transactions.values(
        ):
            # Only if at least one block was received more than 5min after last publishing.
            if (self.lastblock_time > self.last_tx_publish[txhash] + 5 * 60):
                self.fire(self.EVT_PUBLISH_TRANSACTION,
                          txhash=txhash,
                          tx=wallet_tx.merkle_tx.tx)
                self.last_tx_publish[hash] = tnow
        self.schedule_republish_transactions()

    """"""

    def schedule_republish_transactions(self):
        seconds = random.randint(5 * 60, 30 * 60)
        reactor.call_later(seconds, self.republish_transactions)

    def get_receive_address(self):
        public_key = self.wallet.get_receive_key()
        return BitcoinAddress.from_publickey(public_key, self.wallet.runmode)

    def set_receive_label(self, address_base58, label):
        address = BitcoinAddress.from_base58addr(address_base58)
        self.wallet.begin_updates()
        public_key, is_crypted = self.wallet.addresses[
            address]  #decode_base58check(address)[1:]
        self.wallet.allocate_key(public_key, label)
        self.wallet.commit_updates()

        new_description = self.wallet.get_address_description(public_key)
        self.fire(self.EVT_NEW_ADDRESS_DESCRIPTION,
                  public_key=public_key,
                  description=new_description)

    """
    
        amount: value in COIN.
    """

    def create_transaction(self, amount, address, fee):
        outputs = [(outpoint, txout)
                   for (tx, outpoint, txout) in self.iter_my_outputs()]
        selected_outputs = self.coin_selector.select_coins(
            outputs, (amount + fee))

        change_public_key = self.wallet.get_receive_key()
        change_address = BitcoinAddress.from_publickey(change_public_key,
                                                       self.wallet.runmode)
        tx = create_pubkeyhash_transaction(
            selected_outputs,
            address.get_hash160(
            ),  #decode_base58check(address)[1:],  #remove ADDRESSVERSION[runmode] byte
            change_address.get_hash160(
            ),  #decode_base58check(change_address)[1:],  #remove ADDRESSVERSION[runmode] byte
            amount,
            fee)
        return (PlannedTransaction(selected_outputs, amount, address,
                                   change_public_key, change_address, fee, tx))

    def is_passphrase_required(self, planned_transaction):
        for outpoint, txout in planned_transaction.selected_outputs:
            if self.wallet.is_passphrase_required(txout):
                return True
        return False

    def send_transaction(self, planned_tx, passphrases):
        try:
            self.wallet.unlock(passphrases)
            privkey_list = []
            for outpoint, txout in planned_tx.selected_outputs:
                privkey_list.append(
                    self.wallet.get_txout_private_key_secret(txout))
        finally:
            self.wallet.lock()
        sign_transaction(
            planned_tx.tx,
            [txout
             for outpoint, txout in planned_tx.selected_outputs], privkey_list)
        txhash = hash_tx(planned_tx.tx)
        self.log.info(
            "Sending %f to %s (fee:%f), change address: %s, hash:%s" %
            (planned_tx.amount, str(planned_tx.address), planned_tx.fee,
             str(planned_tx.change_address), str(txhash)))
        #Initially, create an empty MerkleTx (the tx is not yet in a block)
        merkle_tx = MerkleTx(planned_tx.tx, Uint256.zero(), [], 4294967295)
        self.wallet.begin_updates()
        self.wallet.allocate_key(planned_tx.change_public_key, ischange=True)
        #Set the spend flags for the input transactions
        for outpoint, txout in planned_tx.selected_outputs:
            input_wallet_tx = self.wallet.get_transaction(outpoint.hash)
            input_wallet_tx.set_spent(outpoint.index)
            self.wallet.set_transaction(outpoint.hash, input_wallet_tx)
        #Add the wallet_tx (contains supporting transations)
        txtime = int(time.time())
        wallet_tx = create_wallet_tx(self.blockchain, merkle_tx, txtime)
        self.wallet.add_transaction(txhash, wallet_tx)
        self.wallet.commit_updates()
        self.fire(self.EVT_NEW_TRANSACTION_ITEM,
                  item=(planned_tx.tx, txhash, txtime, planned_tx.address, "",
                        -planned_tx.amount, False))

        self.compute_balances()  # we could only compute delta here
        self.fire(self.EVT_PUBLISH_TRANSACTION,
                  txhash=txhash,
                  tx=planned_tx.tx)
        self.last_tx_publish[txhash] = txtime
        #update description of change address
        new_description = self.wallet.get_address_description(
            planned_tx.change_public_key)
        self.fire(self.EVT_NEW_ADDRESS_DESCRIPTION,
                  public_key=planned_tx.change_public_key,
                  description=new_description)