def _select_wallet(ctx, address_or_index): try: wallet = Wallet(wallet_path=ctx.obj.wallet_path) if not wallet.addresses: click.echo('This command requires a local wallet') return if wallet.encrypted: secret = click.prompt('The wallet is encrypted. Enter password', hide_input=True) wallet.decrypt(secret) if address_or_index.isdigit(): address_or_index = int(address_or_index) addr_item = get_item_from_wallet(wallet, address_or_index) if addr_item: # FIXME: This should only return pk and index xmss = wallet.get_xmss_by_index(address_or_index) return wallet.addresses[address_or_index], xmss elif address_or_index.startswith('Q'): for i, addr_item in enumerate(wallet.address_items): if address_or_index == addr_item.qaddress: xmss = wallet.get_xmss_by_address(wallet.addresses[i]) return wallet.addresses[i], xmss click.echo('Source address not found in your wallet', color='yellow') quit(1) return parse_qaddress(address_or_index), None except Exception as e: click.echo("Error selecting wallet") click.echo(str(e)) quit(1)
def test_encrypt_save_reload(self): with set_qrl_dir("no_data"): wallet = Wallet() wallet.add_new_address(height=4) wallet.save() wallet_b = Wallet() self.assertEqual(1, len(wallet_b.address_items)) self.assertEqual(wallet.address_items[0], wallet_b.address_items[0]) TEST_KEY = 'mytestkey' wallet_b.encrypt(TEST_KEY) wallet_b.save() self.assertEqual(1, len(wallet_b.address_items)) self.assertNotEqual(wallet.address_items[0], wallet_b.address_items[0]) wallet_c = Wallet() self.assertEqual(1, len(wallet_c.address_items)) self.assertTrue(wallet_c.address_items[0].encrypted) wallet_c.decrypt(TEST_KEY) self.assertFalse(wallet_c.address_items[0].encrypted) self.assertEqual(wallet.address_items[0], wallet_c.address_items[0])
def test_read_wallet_secure_ver0_saves_wallet_ver1_encrypted(self): with set_qrl_dir("wallet_secure_ver0"): wallet = Wallet() self.assertEqual(wallet.version, 0) # Wallet will not let you save an encrypted ver0 wallet as ver1. You have to decrypt it first. # This is because Qaddress is unencrypted in the ver1 wallet. with self.assertRaises(WalletVersionError): wallet.save() wallet.decrypt('test1234') wallet.encrypt('test1234') wallet.version = 1 wallet.save() wallet_reloaded = Wallet() self.assertEqual(wallet_reloaded.version, 1) self.assertEqual(wallet_reloaded.address_items[0].qaddress, 'Q010400d9f1efe5b272e042dcc8ef690f0e90ca8b0b6edba0d26f81e7aff12a6754b21788169f7f') wallet_reloaded.decrypt('test1234') addr_item = wallet_reloaded.address_items[0] self.assertEqual('Q010400d9f1efe5b272e042dcc8ef690f0e90ca8b0b6edba0d26f81e7aff12a6754b21788169f7f', addr_item.qaddress) xmss0 = wallet_reloaded.get_xmss_by_index(0) self.assertEqual('010400d9f1efe5b272e042dcc8ef690f0e90ca8b0b6edba0d26f81e7aff12a6754b21788169f7f', bin2hstr(xmss0.address)) xmss0b = wallet_reloaded.get_xmss_by_address(xmss0.address) self.assertEqual('010400d9f1efe5b272e042dcc8ef690f0e90ca8b0b6edba0d26f81e7aff12a6754b21788169f7f', bin2hstr(xmss0b.address))
def wallet_decrypt(ctx): wallet = Wallet(wallet_path=ctx.obj.wallet_path) click.echo('Decrypting wallet at {}'.format(wallet.wallet_path)) secret = click.prompt('Enter password', hide_input=True, confirmation_prompt=True) wallet.decrypt(secret) wallet.save()
def test_decrypt_wallet(self): with set_qrl_dir("no_data"): wallet = Wallet() wallet.add_new_address(height=4) wallet.add_new_address(height=4) addresses = wallet.addresses TEST_KEY = 'mytestkey' wallet.encrypt(TEST_KEY) self.assertTrue(wallet.encrypted) wallet.decrypt(TEST_KEY) self.assertEqual(addresses, wallet.addresses) self.assertFalse(wallet.encrypted_partially)
def wallet_secret(ctx, wallet_idx): """ Provides the mnemonic/hexseed of the given address index """ wallet = Wallet(wallet_path=ctx.obj.wallet_path) if wallet.encrypted: secret = click.prompt('The wallet is encrypted. Enter password', hide_input=True) wallet.decrypt(secret) address_item = get_item_from_wallet(wallet, wallet_idx) if address_item: click.echo('Wallet Address : {}'.format(address_item.qaddress)) click.echo('Mnemonic : {}'.format(address_item.mnemonic)) click.echo('Hexseed : {}'.format(address_item.hexseed))
def wallet_add(ctx, height, hash_function): """ Adds an address or generates a new wallet (working directory) """ secret = None wallet = Wallet(wallet_path=ctx.obj.wallet_path) wallet_was_encrypted = wallet.encrypted if wallet.encrypted: secret = click.prompt('The wallet is encrypted. Enter password', hide_input=True) wallet.decrypt(secret) wallet.add_new_address(height, hash_function) _print_addresses(ctx, wallet.address_items, config.user.wallet_dir) if wallet_was_encrypted: wallet.encrypt(secret) wallet.save()
def wallet_decrypt(ctx): wallet = Wallet(wallet_path=ctx.obj.wallet_path) click.echo('Decrypting wallet at {}'.format(wallet.wallet_path)) secret = click.prompt('Enter password', hide_input=True) try: wallet.decrypt(secret) except WalletDecryptionError as e: click.echo(str(e)) quit(1) except Exception as e: click.echo(str(e)) quit(1) try: wallet.save() except Exception as e: click.echo(str(e)) quit(1)
def wallet_secret(ctx, wallet_idx): """ Provides the mnemonic/hexseed of the given address index """ if ctx.obj.remote: click.echo('This command is unsupported for remote wallets') return wallet = Wallet(wallet_path=ctx.obj.wallet_path) if wallet.encrypted: secret = click.prompt('The wallet is encrypted. Enter password', hide_input=True) wallet.decrypt(secret) if 0 <= wallet_idx < len(wallet.address_items): address_item = wallet.address_items[wallet_idx] click.echo('Wallet Address : %s' % (address_item.qaddress)) click.echo('Mnemonic : %s' % (address_item.mnemonic)) click.echo('Hexseed : %s' % (address_item.hexseed)) else: click.echo('Wallet index not found', color='yellow')
def test_integrity_behaviour(self): with set_qrl_dir("no_data"): TEST_KEY = 'mytestkey' wallet = Wallet() wallet.add_new_address(4) wallet.add_new_address(4) wallet.encrypt(TEST_KEY) # An encrypted wallet is not editable. with self.assertRaises(WalletEncryptionError): wallet.add_new_address(4) # You may not re-encrypt it. with self.assertRaises(WalletEncryptionError): wallet.encrypt(TEST_KEY) # You can save it. wallet.save() wallet.decrypt_item(1, TEST_KEY) # A partially encrypted wallet is not editable. with self.assertRaises(WalletEncryptionError): wallet.add_new_address(4) # You may not re-encrypt it. with self.assertRaises(WalletEncryptionError): wallet.encrypt(TEST_KEY) # You may not re-decrypt it. with self.assertRaises(WalletEncryptionError): wallet.decrypt(TEST_KEY) # You can't even save it. with self.assertRaises(WalletEncryptionError): wallet.save() wallet.decrypt_item(0, TEST_KEY) # A fully decrypted wallet is editable. wallet.add_new_address(4) # You may not re-decrypt it. with self.assertRaises(WalletEncryptionError): wallet.decrypt(TEST_KEY) # You can save it. wallet.save()
class WalletD: def __init__(self): self._wallet_path = os.path.join(config.user.wallet_dir, 'walletd.json') self._public_stub = qrl_pb2_grpc.PublicAPIStub( grpc.insecure_channel(config.user.public_api_server)) self._wallet = None self._passphrase = None self.load_wallet() def to_plain_blocks(self, block): pheader = qrlwallet_pb2.PlainBlockHeader() pheader.hash_header = bin2hstr(block.header.hash_header) pheader.block_number = block.header.block_number pheader.timestamp_seconds = block.header.timestamp_seconds pheader.hash_header_prev = bin2hstr(block.header.hash_header_prev) pheader.reward_block = block.header.reward_block pheader.reward_fee = block.header.reward_fee pheader.merkle_root = bin2hstr(block.header.merkle_root) pheader.mining_nonce = block.header.mining_nonce pheader.extra_nonce = block.header.extra_nonce pblock = qrlwallet_pb2.PlainBlock() pblock.header.MergeFrom(pheader) for tx in block.transactions: pblock.transactions.extend([self.to_plain_transaction(tx)]) for genesis_balance in block.genesis_balance: pgb = qrlwallet_pb2.PlainGenesisBalance() pgb.address = self.address_to_qaddress(genesis_balance.address) pgb.balance = genesis_balance.balance pblock.genesis_balance.extend([pgb]) return pblock @staticmethod def to_plain_address_amount(address_amount): am = qrlwallet_pb2.PlainAddressAmount() am.address = bin2hstr(address_amount.address) am.amount = address_amount.amount return am def to_plain_transaction(self, tx): ptx = qrlwallet_pb2.PlainTransaction() if not tx.WhichOneof('transactionType'): return ptx if tx.master_addr: ptx.master_addr = self.address_to_qaddress(tx.master_addr) ptx.fee = tx.fee ptx.public_key = bin2hstr(tx.public_key) ptx.signature = bin2hstr(tx.signature) ptx.nonce = tx.nonce ptx.transaction_hash = bin2hstr(tx.transaction_hash) if tx.WhichOneof('transactionType') != 'coinbase': ptx.signer_addr = self.get_address_from_pk(ptx.public_key) if tx.WhichOneof('transactionType') == "transfer": ptx.transfer.amounts.extend(tx.transfer.amounts) for addr in tx.transfer.addrs_to: ptx.transfer.addrs_to.extend([self.address_to_qaddress(addr)]) elif tx.WhichOneof('transactionType') == 'coinbase': ptx.coinbase.addr_to = self.address_to_qaddress( tx.coinbase.addr_to) ptx.coinbase.amount = tx.coinbase.amount elif tx.WhichOneof('transactionType') == 'lattice_public_key': ptx.lattice_public_key.MergeFrom(ptx.lattice_public_key()) ptx.lattice_public_key.kyber_pk = bin2hstr( tx.lattice_public_key.kyber_pk) ptx.lattice_public_key.dilithium_pk = bin2hstr( tx.lattice_public_key.dilithium_pk) elif tx.WhichOneof('transactionType') == 'message': ptx.message.message_hash = tx.message.message_hash elif tx.WhichOneof('transactionType') == 'token': ptx.token.symbol = tx.token.symbol ptx.token.name = tx.token.name ptx.token.owner = self.address_to_qaddress(tx.token.owner) ptx.token.decimals = tx.token.decimals for initial_balance in tx.token.initial_balances: ptx.token.initial_balances.extend( [self.to_plain_address_amount(initial_balance)]) elif tx.WhichOneof('transactionType') == 'transfer_token': ptx.transfer_token.token_txhash = bin2hstr( tx.transfer_token.token_txhash) ptx.transfer_token.addrs_to.extend( self.addresses_to_qaddress(tx.transfer_token.addrs_to)) ptx.transfer_token.amounts.extend(tx.transfer_token.amounts) elif tx.WhichOneof('transactionType') == 'slave': for slave_pk in tx.slave.slave_pks: ptx.slave.slave_pks.extend([bin2hstr(slave_pk)]) ptx.slave.access_types.extend(tx.slave.access_types) return ptx def load_wallet(self): self._wallet = Wallet(self._wallet_path) @staticmethod def address_to_qaddress(address: bytes): return 'Q' + bin2hstr(address) @staticmethod def addresses_to_qaddress(addresses: list): qaddresses = [] for address in addresses: qaddresses.append(WalletD.address_to_qaddress(address)) return qaddresses @staticmethod def qaddress_to_address(qaddress: str) -> bytes: if not qaddress: return qaddress return bytes(hstr2bin(qaddress[1:])) @staticmethod def qaddresses_to_address(qaddresses: list) -> list: if not qaddresses: return qaddresses addresses = [] for qaddress in qaddresses: addresses.append(WalletD.qaddress_to_address(qaddress)) return addresses def authenticate(self): if not self._passphrase: if self._wallet.is_encrypted(): raise ValueError('Failed: Passphrase Missing') def _encrypt_last_item(self): if not self._passphrase: return self._wallet.encrypt_item( len(self._wallet.address_items) - 1, self._passphrase) def _get_wallet_index_xmss(self, signer_address: str, ots_index: int): index, _ = self._wallet.get_address_item(signer_address) xmss = self._wallet.get_xmss_by_index(index, self._passphrase) if ots_index > 0: xmss.set_ots_index(ots_index) return index, xmss def add_new_address(self, height=10, hash_function='shake128') -> str: self.authenticate() if not hash_function: hash_function = 'shake128' if not height: height = 10 self._wallet.add_new_address(height, hash_function, True) self._encrypt_last_item() self._wallet.save() return self._wallet.address_items[-1].qaddress def add_address_from_seed(self, seed=None) -> str: self.authenticate() words = seed.split() if len(words) == 34: bin_seed = mnemonic2bin(seed) elif len(seed) == 102: bin_seed = hstr2bin(seed) else: raise ValueError("Invalid Seed") address_from_seed = XMSS.from_extended_seed(bin_seed) if self._wallet.get_xmss_by_qaddress(address_from_seed.qaddress, self._passphrase): raise Exception("Address is already in the wallet") self._wallet.append_xmss(address_from_seed) self._encrypt_last_item() self._wallet.save() return address_from_seed.qaddress def list_address(self) -> list: self.authenticate() addresses = [] for item in self._wallet.address_items: addresses.append(item.qaddress) return addresses def remove_address(self, qaddress: str) -> bool: self.authenticate() return self._wallet.remove(qaddress) def get_recovery_seeds(self, qaddress: str): self.authenticate() xmss = self._wallet.get_xmss_by_qaddress(qaddress, self._passphrase) if xmss: return xmss.hexseed, xmss.mnemonic raise ValueError("No such address found in wallet") def get_wallet_info(self): self.authenticate() return self._wallet.wallet_info() def _push_transaction(self, tx, xmss): tx.sign(xmss) if not tx.validate(True): return None push_transaction_req = qrl_pb2.PushTransactionReq( transaction_signed=tx.pbdata) push_transaction_resp = self._public_stub.PushTransaction( push_transaction_req, timeout=CONNECTION_TIMEOUT) if push_transaction_resp.error_code != qrl_pb2.PushTransactionResp.SUBMITTED: raise Exception(push_transaction_resp.error_description) def relay_transfer_txn(self, qaddresses_to: list, amounts: list, fee: int, master_qaddress, signer_address: str, ots_index: int): self.authenticate() index, xmss = self._get_wallet_index_xmss(signer_address, ots_index) tx = TransferTransaction.create( addrs_to=self.qaddresses_to_address(qaddresses_to), amounts=amounts, fee=fee, xmss_pk=xmss.pk, master_addr=self.qaddress_to_address(master_qaddress)) self._push_transaction(tx, xmss) self._wallet.set_ots_index(index, xmss.ots_index) return self.to_plain_transaction(tx.pbdata) def relay_message_txn(self, message: str, fee: int, master_qaddress, signer_address: str, ots_index: int): self.authenticate() index, xmss = self._get_wallet_index_xmss(signer_address, ots_index) tx = MessageTransaction.create( message_hash=message.encode(), fee=fee, xmss_pk=xmss.pk, master_addr=self.qaddress_to_address(master_qaddress)) self._push_transaction(tx, xmss) self._wallet.set_ots_index(index, xmss.ots_index) return self.to_plain_transaction(tx.pbdata) def relay_token_txn(self, symbol: str, name: str, owner_qaddress: str, decimals: int, qaddresses: list, amounts: list, fee: int, master_qaddress, signer_address: str, ots_index: int): self.authenticate() if len(qaddresses) != len(amounts): raise Exception("Number of Addresses & Amounts Mismatch") index, xmss = self._get_wallet_index_xmss(signer_address, ots_index) initial_balances = [] for idx, qaddress in enumerate(qaddresses): initial_balances.append( qrl_pb2.AddressAmount( address=self.qaddress_to_address(qaddress), amount=amounts[idx])) tx = TokenTransaction.create( symbol=symbol.encode(), name=name.encode(), owner=self.qaddress_to_address(owner_qaddress), decimals=decimals, initial_balances=initial_balances, fee=fee, xmss_pk=xmss.pk, master_addr=self.qaddress_to_address(master_qaddress)) self._push_transaction(tx, xmss) self._wallet.set_ots_index(index, xmss.ots_index) return self.to_plain_transaction(tx.pbdata) def relay_transfer_token_txn(self, qaddresses_to: list, amounts: list, token_txhash: str, fee: int, master_qaddress, signer_address: str, ots_index: int): self.authenticate() index, xmss = self._get_wallet_index_xmss(signer_address, ots_index) tx = TransferTokenTransaction.create( token_txhash=bytes(hstr2bin(token_txhash)), addrs_to=self.qaddresses_to_address(qaddresses_to), amounts=amounts, fee=fee, xmss_pk=xmss.pk, master_addr=self.qaddress_to_address(master_qaddress)) self._push_transaction(tx, xmss) self._wallet.set_ots_index(index, xmss.ots_index) return self.to_plain_transaction(tx.pbdata) def relay_slave_txn(self, slave_pks: list, access_types: list, fee: int, master_qaddress, signer_address: str, ots_index: int): self.authenticate() index, xmss = self._get_wallet_index_xmss(signer_address, ots_index) tx = SlaveTransaction.create( slave_pks=slave_pks, access_types=access_types, fee=fee, xmss_pk=xmss.pk, master_addr=self.qaddress_to_address(master_qaddress)) self._push_transaction(tx, xmss) self._wallet.set_ots_index(index, xmss.ots_index) return self.to_plain_transaction(tx.pbdata) def encrypt_wallet(self, passphrase: str): if self._wallet.is_encrypted(): raise Exception('Wallet Already Encrypted') if not passphrase: raise Exception("Missing Passphrase") if len(self._wallet.address_items) == 0: raise ValueError( 'Cannot be encrypted as wallet does not have any address.') self._wallet.encrypt(passphrase) self._wallet.save() def lock_wallet(self): self._passphrase = None def unlock_wallet(self, passphrase: str): self._passphrase = passphrase self._wallet.decrypt(passphrase, first_address_only=True) self.load_wallet() def change_passphrase(self, old_passphrase: str, new_passphrase: str): if len(old_passphrase) == 0: raise Exception('Missing Old Passphrase') if len(new_passphrase) == 0: raise Exception('Missing New Passphrase') if old_passphrase == new_passphrase: raise Exception('Old Passphrase and New Passphrase cannot be same') self._passphrase = old_passphrase if not self._wallet: self.unlock_wallet(old_passphrase) try: self._wallet.decrypt(old_passphrase) except WalletDecryptionError: raise ValueError('Invalid Old Passphrase') self._wallet.encrypt(new_passphrase) self._wallet.save() self.lock_wallet() def get_transactions_by_address(self, qaddress: str) -> tuple: address = self.qaddress_to_address(qaddress) response = self._public_stub.GetTransactionsByAddress( qrl_pb2.GetTransactionsByAddressReq(address=address)) return response.mini_transactions, response.balance def get_transaction(self, tx_hash: str): txhash = bytes(hstr2bin(tx_hash)) response = self._public_stub.GetTransaction( qrl_pb2.GetTransactionReq(tx_hash=txhash)) return self.to_plain_transaction(response.tx), response.confirmations def get_balance(self, qaddress: str) -> int: address = self.qaddress_to_address(qaddress) response = self._public_stub.GetBalance( qrl_pb2.GetBalanceReq(address=address)) return response.balance def get_ots(self, qaddress: str): address = self.qaddress_to_address(qaddress) response = self._public_stub.GetOTS(qrl_pb2.GetOTSReq(address=address)) return response.ots_bitfield, response.next_unused_ots_index def get_height(self) -> int: response = self._public_stub.GetHeight(qrl_pb2.GetHeightReq()) return response.height def get_block(self, header_hash: str): headerhash = bytes(hstr2bin(header_hash)) response = self._public_stub.GetBlock( qrl_pb2.GetBlockReq(header_hash=headerhash)) return self.to_plain_blocks(response.block) def get_block_by_number(self, block_number: int): response = self._public_stub.GetBlockByNumber( qrl_pb2.GetBlockByNumberReq(block_number=block_number)) return self.to_plain_blocks(response.block) def get_address_from_pk(self, pk: str) -> str: return self.address_to_qaddress( QRLHelper.getAddress(bytes(hstr2bin(pk))))