diff --git a/lib/ucoinpy/documents/_ucoinpy_test/documents/test_transaction.py b/lib/ucoinpy/documents/_ucoinpy_test/documents/test_transaction.py new file mode 100644 index 0000000000000000000000000000000000000000..ea1691438b80a0faa138146073777aa578bba06b --- /dev/null +++ b/lib/ucoinpy/documents/_ucoinpy_test/documents/test_transaction.py @@ -0,0 +1,137 @@ +''' +Created on 12 déc. 2014 + +@author: inso +''' +import pytest +from ucoinpy.documents.transaction import Transaction +from mock import Mock + + +tx_compact = """TX:1:1:3:1:0 +HsLShAtzXTVxeUtQd7yi5Z5Zh4zNvbu8sTEZ53nfKcqY +0:T:65:D717FEC1993554F8EAE4CEA88DE5FBB6887CFAE8:4 +0:T:77:F80993776FB55154A60B3E58910C942A347964AD:15 +0:D:88:F4A47E39BC2A20EE69DCD5CAB0A9EB3C92FD8F7B:11 +BYfWYFrsyjpvpFysgu19rGK3VHBkz4MqmQbNyEuVU64g:30 +42yQm4hGTJYWkPg39hQAUgP6S6EQ4vTfXdJuxKEHL1ih6YHiDL2hcwrFgBHjXLRgxRhj2VNVqqc6b4JayKqTE14r +""" + +tx_raw = """Version: 1 +Type: Transaction +Currency: beta_brousouf +Issuers: +HsLShAtzXTVxeUtQd7yi5Z5Zh4zNvbu8sTEZ53nfKcqY +Inputs: +0:T:65:D717FEC1993554F8EAE4CEA88DE5FBB6887CFAE8:4 +0:T:77:F80993776FB55154A60B3E58910C942A347964AD:15 +0:D:88:F4A47E39BC2A20EE69DCD5CAB0A9EB3C92FD8F7B:11 +Outputs: +BYfWYFrsyjpvpFysgu19rGK3VHBkz4MqmQbNyEuVU64g:30 +Comment: +42yQm4hGTJYWkPg39hQAUgP6S6EQ4vTfXdJuxKEHL1ih6YHiDL2hcwrFgBHjXLRgxRhj2VNVqqc6b4JayKqTE14r +""" + + +class Test_Transaction: + def test_fromcompact(self): + tx = Transaction.from_compact("zeta_brousouf", tx_compact) + assert tx.version == 1 + assert tx.currency == "zeta_brousouf" + assert len(tx.issuers) == 1 + assert len(tx.inputs) == 3 + assert len(tx.outputs) == 1 + + assert tx.issuers[0] == "HsLShAtzXTVxeUtQd7yi5Z5Zh4zNvbu8sTEZ53nfKcqY" + + assert tx.inputs[0].index == 0 + assert tx.inputs[0].source == 'T' + assert tx.inputs[0].number == 65 + assert tx.inputs[0].txhash == "D717FEC1993554F8EAE4CEA88DE5FBB6887CFAE8" + assert tx.inputs[0].amount == 4 + + assert tx.inputs[1].index == 0 + assert tx.inputs[1].source == 'T' + assert tx.inputs[1].number == 77 + assert tx.inputs[1].txhash == "F80993776FB55154A60B3E58910C942A347964AD" + assert tx.inputs[1].amount == 15 + + assert tx.inputs[2].index == 0 + assert tx.inputs[2].source == 'D' + assert tx.inputs[2].number == 88 + assert tx.inputs[2].txhash == "F4A47E39BC2A20EE69DCD5CAB0A9EB3C92FD8F7B" + assert tx.inputs[2].amount == 11 + + assert tx.outputs[0].pubkey == "BYfWYFrsyjpvpFysgu19rGK3VHBkz4MqmQbNyEuVU64g" + assert tx.outputs[0].amount == 30 + + assert tx.signatures[0] == "42yQm4hGTJYWkPg39hQAUgP6S6EQ4vTfXdJuxKEHL1ih6YHiDL2hcwrFgBHjXLRgxRhj2VNVqqc6b4JayKqTE14r" + + def test_fromraw(self): + tx = Transaction.from_signed_raw(tx_raw) + assert tx.version == 1 + assert tx.currency == "beta_brousouf" + assert len(tx.issuers) == 1 + assert len(tx.inputs) == 3 + assert len(tx.outputs) == 1 + + assert tx.issuers[0] == "HsLShAtzXTVxeUtQd7yi5Z5Zh4zNvbu8sTEZ53nfKcqY" + + assert tx.inputs[0].index == 0 + assert tx.inputs[0].source == 'T' + assert tx.inputs[0].number == 65 + assert tx.inputs[0].txhash == "D717FEC1993554F8EAE4CEA88DE5FBB6887CFAE8" + assert tx.inputs[0].amount == 4 + + assert tx.inputs[1].index == 0 + assert tx.inputs[1].source == 'T' + assert tx.inputs[1].number == 77 + assert tx.inputs[1].txhash == "F80993776FB55154A60B3E58910C942A347964AD" + assert tx.inputs[1].amount == 15 + + assert tx.inputs[2].index == 0 + assert tx.inputs[2].source == 'D' + assert tx.inputs[2].number == 88 + assert tx.inputs[2].txhash == "F4A47E39BC2A20EE69DCD5CAB0A9EB3C92FD8F7B" + assert tx.inputs[2].amount == 11 + + assert tx.outputs[0].pubkey == "BYfWYFrsyjpvpFysgu19rGK3VHBkz4MqmQbNyEuVU64g" + assert tx.outputs[0].amount == 30 + + assert tx.signatures[0] == "42yQm4hGTJYWkPg39hQAUgP6S6EQ4vTfXdJuxKEHL1ih6YHiDL2hcwrFgBHjXLRgxRhj2VNVqqc6b4JayKqTE14r" + + def test_fromraw_toraw(self): + tx = Transaction.from_signed_raw(tx_raw) + rendered_tx = tx.signed_raw() + from_rendered_tx = Transaction.from_signed_raw(rendered_tx) + + assert from_rendered_tx.version == 1 + assert len(from_rendered_tx.issuers) == 1 + assert len(from_rendered_tx.inputs) == 3 + assert len(from_rendered_tx.outputs) == 1 + + assert from_rendered_tx.issuers[0] == "HsLShAtzXTVxeUtQd7yi5Z5Zh4zNvbu8sTEZ53nfKcqY" + + assert from_rendered_tx.inputs[0].index == 0 + assert from_rendered_tx.inputs[0].source == 'T' + assert from_rendered_tx.inputs[0].number == 65 + assert from_rendered_tx.inputs[0].txhash == "D717FEC1993554F8EAE4CEA88DE5FBB6887CFAE8" + assert from_rendered_tx.inputs[0].amount == 4 + + assert from_rendered_tx.inputs[1].index == 0 + assert from_rendered_tx.inputs[1].source == 'T' + assert from_rendered_tx.inputs[1].number == 77 + assert from_rendered_tx.inputs[1].txhash == "F80993776FB55154A60B3E58910C942A347964AD" + assert from_rendered_tx.inputs[1].amount == 15 + + assert from_rendered_tx.inputs[2].index == 0 + assert from_rendered_tx.inputs[2].source == 'D' + assert from_rendered_tx.inputs[2].number == 88 + assert from_rendered_tx.inputs[2].txhash == "F4A47E39BC2A20EE69DCD5CAB0A9EB3C92FD8F7B" + assert from_rendered_tx.inputs[2].amount == 11 + + assert from_rendered_tx.outputs[0].pubkey == "BYfWYFrsyjpvpFysgu19rGK3VHBkz4MqmQbNyEuVU64g" + assert from_rendered_tx.outputs[0].amount == 30 + + assert from_rendered_tx.signatures[0] == "42yQm4hGTJYWkPg39hQAUgP6S6EQ4vTfXdJuxKEHL1ih6YHiDL2hcwrFgBHjXLRgxRhj2VNVqqc6b4JayKqTE14r" + diff --git a/lib/ucoinpy/documents/ucoinpy/key/__init__.py b/lib/ucoinpy/documents/ucoinpy/key/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..d28d328fef2302e087fdadd983dc190abd3fe44a --- /dev/null +++ b/lib/ucoinpy/documents/ucoinpy/key/__init__.py @@ -0,0 +1,39 @@ +''' +Ucoin public and private keys + +@author: inso +''' + +import base58 +import base64 +import scrypt +from nacl.signing import SigningKey as NaclSigningKey +from nacl.encoding import Base64Encoder + + +SEED_LENGTH = 32 # Length of the key +crypto_sign_BYTES = 64 +SCRYPT_PARAMS = {'N': 4096, + 'r': 16, + 'p': 1 + } + + +class SigningKey(NaclSigningKey): + def __init__(self, password, salt): + seed = scrypt.hash(password, salt, + SCRYPT_PARAMS['N'], SCRYPT_PARAMS['r'], SCRYPT_PARAMS['p'], + SEED_LENGTH) + seedb64 = base64.b64encode(seed) + super.__init__(seedb64, Base64Encoder) + self.pubkey = Base58Encoder.encode(self.verify_key.key) + + +class Base58Encoder(object): + @staticmethod + def encode(data): + return base58.b58encode(data) + + @staticmethod + def decode(data): + return base58.b58decode(data) diff --git a/res/ui/communityConfigurationDialog.ui b/res/ui/communityConfigurationDialog.ui index 309d2154e357bc67c59d3b3b37ea59e9c5344178..d0202c635b7ed42c1334578915eb92c3365dae4f 100644 --- a/res/ui/communityConfigurationDialog.ui +++ b/res/ui/communityConfigurationDialog.ui @@ -23,7 +23,7 @@ <item> <widget class="QStackedWidget" name="stacked_pages"> <property name="currentIndex"> - <number>0</number> + <number>1</number> </property> <widget class="QWidget" name="page_init"> <layout class="QVBoxLayout" name="verticalLayout_4"> @@ -146,17 +146,6 @@ </item> </layout> </widget> - <widget class="QWidget" name="page_wallets"> - <layout class="QVBoxLayout" name="verticalLayout_5"> - <item> - <widget class="QTabWidget" name="tabs_wallets"> - <property name="currentIndex"> - <number>-1</number> - </property> - </widget> - </item> - </layout> - </widget> </widget> </item> <item> diff --git a/src/cutecoin/gui/processConfigureAccount.py b/src/cutecoin/gui/processConfigureAccount.py index 4f82e6123c56d9deaa72e960a60dee75ba85a3c0..daa35b50282687b3e3fa27aded59ffd67f1c3a1f 100644 --- a/src/cutecoin/gui/processConfigureAccount.py +++ b/src/cutecoin/gui/processConfigureAccount.py @@ -72,7 +72,8 @@ class StepPageKey(Step): def process_next(self): salt = self.config_dialog.edit_email.text() password = self.config_dialog.edit_password.text() - self.config_dialog.account.key = SigningKey(salt, password) + self.config_dialog.account.salt = salt + self.config_dialog.account.pubkey = SigningKey(salt, password).public_key model = CommunitiesListModel(self.config_dialog.account) self.config_dialog.list_communities.setModel(model) @@ -101,7 +102,7 @@ class StepPageCommunities(Step): account = self.config_dialog.account self.config_dialog.community = account.communities.add_community( default_node) - account.wallets.add_wallet(account.key, + account.wallets.add_wallet(0, self.config_dialog.community) self.config_dialog.refresh() @@ -147,7 +148,10 @@ class ProcessConfigureAccount(QDialog, Ui_AccountConfigurationDialog): dialog.exec_() def action_add_community(self): + logging.debug("Action add community : done") self.list_communities.setModel(CommunitiesListModel(self.account)) + self.button_next.setEnabled(True) + self.button_next.setText("Ok") def action_remove_community(self): for index in self.list_communities.selectedIndexes(): diff --git a/src/cutecoin/gui/processConfigureCommunity.py b/src/cutecoin/gui/processConfigureCommunity.py index 8735a99b709b3124832abc03f55eade33a68994d..68af5ea8a5b691f2df5a305a56d8f90bc4e4839f 100644 --- a/src/cutecoin/gui/processConfigureCommunity.py +++ b/src/cutecoin/gui/processConfigureCommunity.py @@ -11,7 +11,7 @@ from PyQt5.QtWidgets import QDialog, QMenu, QMessageBox, QWidget, QAction from PyQt5.QtCore import QSignalMapper from cutecoin.models.node.treeModel import NodesTreeModel from cutecoin.models.node import Node -from cutecoin.gui.walletTabWidget import WalletTabWidget +from cutecoin.tools.exceptions import Error class Step(): @@ -78,32 +78,7 @@ class StepPageAddNodes(Step): self.config_dialog.community.name()) self.config_dialog.tree_nodes.setModel(tree_model) self.config_dialog.button_previous.setEnabled(False) - self.config_dialog.button_next.setText("Next") - - -class StepPageSetWallets(Step): - ''' - The step where the user set his wallets - ''' - def __init__(self, config_dialog): - super().__init__(config_dialog) - - def is_valid(self): - return True - - def display_page(self): - self.config_dialog.tabs_wallets.clear() - signal_mapper = QSignalMapper(self.config_dialog) - - self.config_dialog.tabs_wallets.addTab(QWidget(), "+") - - self.config_dialog.button_previous.setEnabled(True) self.config_dialog.button_next.setText("Ok") - changed_slot = self.config_dialog.current_wallet_changed - self.config_dialog.tabs_wallets.currentChanged.connect(changed_slot) - - def process_next(self): - pass class ProcessConfigureCommunity(QDialog, Ui_CommunityConfigurationDialog): @@ -128,11 +103,8 @@ class ProcessConfigureCommunity(QDialog, Ui_CommunityConfigurationDialog): step_init = StepPageInit(self) step_add_nodes = StepPageAddNodes(self) - step_set_wallets = StepPageSetWallets(self) step_init.next_step = step_add_nodes - step_add_nodes.next_step = step_set_wallets - step_set_wallets.previous_step = step_add_nodes if self.community is not None: self.stacked_pages.removeWidget(self.page_init) @@ -175,11 +147,6 @@ class ProcessConfigureCommunity(QDialog, Ui_CommunityConfigurationDialog): self.tree_nodes.setModel(NodesTreeModel(self.community, self.nodes)) - def add_wallet(self): - self.wallet_edit[self.account.wallets[-1].name] = True - self.tabs_wallets.disconnect() - self.step.display_page() - def showContextMenu(self, point): if self.stacked_pages.currentIndex() == 1: menu = QMenu() @@ -189,21 +156,12 @@ class ProcessConfigureCommunity(QDialog, Ui_CommunityConfigurationDialog): action.setEnabled(False) menu.exec_(self.mapToGlobal(point)) - def wallet_edited(self, name): - self.wallet_edit[name] = True - def accept(self): - result = self.account.send_pubkey(self.community) - if result: + try: + self.account.send_pubkey(self.community) + except Error as e: QMessageBox.critical(self, "Pubkey publishing error", - result) - - for wallet in self.account.wallets: - if self.wallet_edit[wallet.name]: - result = wallet.push_wht(self.account.gpg) - if result: - QMessageBox.critical(self, "Wallet publishing error", - result) + e.message) self.accepted.emit() self.close() diff --git a/src/cutecoin/models/account/__init__.py b/src/cutecoin/models/account/__init__.py index 6d8d1e7aa3a68708f308308a9fa81058937ed61c..ea510836b682a1f019ef818fd9dee149b5d257d8 100644 --- a/src/cutecoin/models/account/__init__.py +++ b/src/cutecoin/models/account/__init__.py @@ -24,27 +24,29 @@ class Account(object): be locally referenced by only one account. ''' - def __init__(self, key, name, communities, wallets, contacts): + def __init__(self, salt, pubkey, name, communities, wallets, contacts): ''' Constructor ''' - self.key = key + self.salt = salt + self.pubkey = pubkey self.name = name self.communities = communities self.wallets = wallets self.contacts = contacts @classmethod - def create(cls, name, communities, wallets, confpath): + def create(cls, salt, pubkey, name, communities, wallets, confpath): ''' Constructor ''' - account = cls(None, name, communities, wallets, []) + account = cls(salt, pubkey, name, communities, wallets, []) return account @classmethod - def load(cls, passwd, json_data): + def load(cls, json_data): salt = json_data['salt'] + pubkey = json_data['pubkey'] name = json_data['name'] contacts = [] @@ -55,7 +57,7 @@ class Account(object): wallets = Wallets.load(json_data['wallets']) communities = Communities.load(json_data['communities']) - account = cls(SigningKey(passwd, salt), name, communities, wallets, contacts) + account = cls(salt, pubkey, name, communities, wallets, contacts) return account def __eq__(self, other): @@ -74,7 +76,7 @@ class Account(object): currency = block_data['currency'] logging.debug("Currency : {0}".format(currency)) community = self.communities.add_community(currency, default_node) - self.wallets.add_wallet(currency) + self.wallets.add_wallet(self.wallets.nextid(), currency) return community def transactions_received(self): @@ -109,6 +111,7 @@ class Account(object): def jsonify(self): data = {'name': self.name, 'salt': self.salt, + 'pubkey': self.pubkey, 'communities': self.communities.jsonify(), 'wallets': self.wallets.jsonify(), 'contacts': self.jsonify_contacts()} diff --git a/src/cutecoin/models/account/communities/__init__.py b/src/cutecoin/models/account/communities/__init__.py index 7035dccf02d05b1826872d32e465fbbc1af6111e..d2c1fe2efa682bcf0c9b17c6e7aa3bb01814f940 100644 --- a/src/cutecoin/models/account/communities/__init__.py +++ b/src/cutecoin/models/account/communities/__init__.py @@ -45,11 +45,11 @@ class Communities(object): def __getitem__(self, key): return self._communities_list[key] - def add_community(self, wallets): + def add_community(self, currency, default_node): ''' Add a community with a mainNode ''' - community = Community.create(wallets) + community = Community.create(currency, default_node) if community not in self._communities_list: self._communities_list.append(community) return community diff --git a/src/cutecoin/models/account/wallets/__init__.py b/src/cutecoin/models/account/wallets/__init__.py index 4bc7b0d356a1c617586d675ded06af60b51cb1f1..82e6074d0c36146929e155e9e60e2a6dbbd7f744 100644 --- a/src/cutecoin/models/account/wallets/__init__.py +++ b/src/cutecoin/models/account/wallets/__init__.py @@ -46,27 +46,24 @@ class Wallets(object): def __getitem__(self, key): return self._wallets_list[key] - def add_wallet(self, gpg, keyid, currency, node, - required_trusts=1, name="Main Wallet"): + def add_wallet(self, walletid, currency, name="Main Wallet"): ''' Create a new wallet of a specific currency. ''' - wallet = Wallet.create(keyid, currency, node, - required_trusts, name) - # We try to add already present nodes to the wallet - present_nodes = wallet.get_nodes_in_peering(wallet.pull_wht(gpg)) - if present_nodes is None: - present_nodes = [] - - for present_node in present_nodes: - if present_node not in wallet.nodes: - wallet.nodes.append(present_node) - + wallet = Wallet.create(walletid, currency, name) if wallet not in self._wallets_list: self._wallets_list.append(wallet) return wallet else: return self._wallets_list.get(wallet) + + def nextid(self): + walletid = 0 + for w in self._wallets_list: + if w.id >= id: + walletid = w.id + 1 + return walletid + def jsonify(self): ''' Return the list of wallets in a key:value form. diff --git a/src/cutecoin/models/community/__init__.py b/src/cutecoin/models/community/__init__.py index 8fc1633296d953736022fff7b896b851bd1d88b7..81f6b9b179b2e7dc1be6810bbe2a60f14bad0c2c 100644 --- a/src/cutecoin/models/community/__init__.py +++ b/src/cutecoin/models/community/__init__.py @@ -26,7 +26,7 @@ class Community(object): @classmethod def create(cls, currency, default_node): - return cls(currency, default_node) + return cls(currency, [default_node]) @classmethod def load(cls, json_data): @@ -50,7 +50,7 @@ class Community(object): current_amendment = self.request(bma.blockchain.Current()) return int(current_amendment['du']) - def send_pubkey(self, account, wallets): + def send_pubkey(self, account): pass def send_membership(self, account, membership): diff --git a/src/cutecoin/models/node/__init__.py b/src/cutecoin/models/node/__init__.py index 2d3d85ba46d9616c35465ad76183669352aae81f..eeb94a1c4b5a3e8b6e126f1a877bb47298b1d3da 100644 --- a/src/cutecoin/models/node/__init__.py +++ b/src/cutecoin/models/node/__init__.py @@ -4,7 +4,9 @@ Created on 1 févr. 2014 @author: inso ''' +import logging from ucoinpy.api import bma +from ucoinpy.documents.peer import Endpoint, BMAEndpoint, UnknownEndpoint import re @@ -31,6 +33,17 @@ class Node(object): port = json_data['port'] return cls(server, port) + @classmethod + def from_endpoint(cls, endpoint_data): + endpoint = Endpoint.from_inline(endpoint_data) + if type(endpoint) is not UnknownEndpoint: + if type(endpoint) is BMAEndpoint: + if endpoint.server: + return cls(Node(endpoint.server, endpoint.port)) + else: + return cls(Node(endpoint.ipv4, endpoint.port)) + return None + def __eq__(self, other): pubkey = bma.network.Peering(server=self.server, port=self.port).get()['puubkey'] @@ -47,7 +60,10 @@ class Node(object): def peers(self): request = bma.network.peering.Peers(self.connection_handler()) - return request.get() + peer_nodes = [] + for peer in request.get(): + logging.debug(peer) + return peer_nodes def connection_handler(self): if self.server is not None: diff --git a/src/cutecoin/models/wallet/__init__.py b/src/cutecoin/models/wallet/__init__.py index bb31f0ec889e1e95b52149c2a6e8e4876cf67304..416325c9c163aed2302e7d67071466fdae905245 100644 --- a/src/cutecoin/models/wallet/__init__.py +++ b/src/cutecoin/models/wallet/__init__.py @@ -25,12 +25,12 @@ class Wallet(object): It's only used to sort coins. ''' - def __init__(self, key, currency, name): + def __init__(self, walletid, currency, name): ''' Constructor ''' self.coins = [] - self.key = key + self.walletid = walletid self.currency = currency self.name = name @@ -39,11 +39,11 @@ class Wallet(object): return cls(walletid, currency, name) @classmethod - def load(cls, passwd, json_data): + def load(cls, json_data): walletid = json_data['id'] name = json_data['name'] currency = json_data['currency'] - return cls(SigningKey(walletid, passwd), currency, name) + return cls(walletid, currency, name) def __eq__(self, other): return (self.keyid == other.keyid) @@ -81,8 +81,7 @@ class Wallet(object): return data def jsonify(self): - return {'required_trusts': self.required_trusts, - 'key': self.key, + return {'walletid': self.walletid, 'name': self.name, 'currency': self.currency, 'nodes': self.jsonify_nodes_list()}