diff --git a/lib/ucoin/__init__.py b/lib/ucoin/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..6f72c61da64512c194e3d338a9e4bc463d1c4e46 --- /dev/null +++ b/lib/ucoin/__init__.py @@ -0,0 +1,211 @@ +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# +# Authors: +# Caner Candan <caner@candan.fr>, http://caner.candan.fr +# + +__all__ = ['api'] + +__author__ = 'uCoin team' +__version__ = '0.2' +__nonsense__ = 'uCoin' + +import requests +import logging +import gnupg +import json + +settings = { + 'server': 'localhost', + 'port': 8081, + 'auth': False, +} + +logger = logging.getLogger("ucoin") + + +class Response: + """Wrapper of requests.Response class in order to verify signed message.""" + + def __init__(self, response): + """ + Arguments: + - `self`: + - `response`: + """ + + self.response = response + self.status_code = response.status_code + self.headers = response.headers + + if settings.get('auth'): + self.verified, clear, self.signature = self.split_n_verify(response) + + if not self.verified: + raise ValueError('bad signature verification') + + self.text = self.clear_text = clear + self.content = self.clear_content = self.text.encode('ascii') + else: + self.text = response.text + self.content = response.content + + def json(self): + if not settings.get('auth'): + return self.response.json() + + return json.loads(self.text) + + def split_n_verify(self, response): + """ + Split the signed message thanks to the boundary + value got in content-type header. + + returns a tuple with the status, the clear message and the signature. + + `response`: the response returns by requests.get() needed + to access to headers and response content. + """ + + begin = '-----BEGIN PGP SIGNATURE-----' + end = '-----END PGP SIGNATURE-----' + boundary_pattern = 'boundary=' + + content_type = response.headers['content-type'] + boundary = content_type[content_type.index(boundary_pattern)+len(boundary_pattern):] + boundary = boundary[:boundary.index(';')].strip() + + data = [x.strip() for x in response.text.split('--%s' % boundary)] + + clear = data[1] + signed = data[2][data[2].index(begin):] + clearsigned = '-----BEGIN PGP SIGNED MESSAGE-----\nHash: SHA1\n\n%s\n%s' % (clear, signed) + + return (bool(settings['gpg'].verify(clearsigned)), clear, signed) + + +class API: + """APIRequest is a class used as an interface. + The intermediate derivated classes are the modules + and the leaf classes are the API requests.""" + + def __init__(self, module, server=None, port=None): + """ + Asks a module in order to create the url + used then by derivated classes. + + Arguments: + - `module`: module name + """ + + self.module = module + self.server = server + self.port = port + + self.headers = {} + + if settings['auth']: + self.headers['Accept'] = 'multipart/signed' + + def reverse_url(self, path): + """ + Reverses the url using self.url and path given in parameter. + + Arguments: + - `path`: the request path + """ + + url = 'http://%s:%d/%s' % (self.server if self.server + else settings['server'], + self.port if self.port + else settings['port'], + self.module) + return url + path + + def get(self, **kwargs): + """wrapper of overloaded __get__ method.""" + + return self.__get__(**kwargs) + + def post(self, **kwargs): + """wrapper of overloaded __post__ method.""" + + logger.debug('do some work with') + + data = self.__post__(**kwargs) + + logger.debug('and send back') + + return data + + def __get__(self, **kwargs): + """interface purpose for GET request""" + + pass + + def __post__(self, **kwargs): + """interface purpose for POST request""" + + pass + + def requests_get(self, path, **kwargs): + """ + Requests GET wrapper in order to use API parameters. + + Arguments: + - `path`: the request path + """ + + response = None + + if not settings.get('auth'): + response = requests.get(self.reverse_url(path), params=kwargs, + headers=self.headers) + else: + response = Response(requests.get(self.reverse_url(path), + params=kwargs, headers=self.headers)) + + if response.status_code != 200: + raise ValueError('status code != 200 => %d (%s)' + % (response.status_code, response.text)) + + return response + + def requests_post(self, path, **kwargs): + """ + Requests POST wrapper in order to use API parameters. + + Arguments: + - `path`: the request path + """ + + response = requests.post(self.reverse_url(path), + data=kwargs, headers=self.headers) + + if response.status_code != 200: + raise ValueError('status code != 200 => %d (%s)' + % (response.status_code, response.text)) + + return response + + def merkle_easy_parser(self, path, begin=None, end=None): + root = self.requests_get(path, leaves='true').json() + for leaf in root['leaves'][begin:end]: + yield self.requests_get(path, leaf=leaf).json()['leaf'] + +from . import pks +from . import hdc +from . import wrappers +from . import network diff --git a/lib/ucoin/hdc/__init__.py b/lib/ucoin/hdc/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..fbfe1cd3df612ff8ef2dc2915c6cf2d8a7265681 --- /dev/null +++ b/lib/ucoin/hdc/__init__.py @@ -0,0 +1,29 @@ +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# +# Authors: +# Caner Candan <caner@candan.fr>, http://caner.candan.fr +# + +from .. import API +from .. import logging + +logger = logging.getLogger("ucoin/hdc") + + +class HDC(API): + def __init__(self, module='hdc', server=None, port=None): + super().__init__(module, server, port) + +from . import amendments, coins, transactions diff --git a/lib/ucoin/hdc/amendments/__init__.py b/lib/ucoin/hdc/amendments/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..eada4013355063d5c43b3a1eb76e2bfc49a45f56 --- /dev/null +++ b/lib/ucoin/hdc/amendments/__init__.py @@ -0,0 +1,66 @@ +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# +# Authors: +# Caner Candan <caner@candan.fr>, http://caner.candan.fr +# + +from .. import HDC +from .. import logging + +logger = logging.getLogger("ucoin/hdc/amendments") + + +class Base(HDC): + def __init__(self, server=None, port=None): + super().__init__('hdc/amendments', server, port) + + +class Promoted(Base): + """GET the current promoted amendment (amendment + which received enough votes to be accepted).""" + + def __init__(self, number=None, server=None, port=None): + """ + Uses number to fit the result. + + Arguments: + - `number`: amendment number + """ + + super().__init__(server, port) + + self.number = number + + def __get__(self, **kwargs): + if not self.number: + return self.requests_get('/promoted', **kwargs).json() + + return self.requests_get('/promoted/%d' % self.number, **kwargs).json() + + +class Votes(Base): + """GET an index of votes received by this node.""" + + def __get__(self, **kwargs): + return self.requests_get('/votes', **kwargs).json() + + def __post__(self, **kwargs): + assert 'amendment' in kwargs + assert 'signature' in kwargs + assert 'peer' in kwargs + + return self.requests_post('/votes', **kwargs).json() + +from . import view diff --git a/lib/ucoin/hdc/amendments/view.py b/lib/ucoin/hdc/amendments/view.py new file mode 100644 index 0000000000000000000000000000000000000000..02a74f1eea3a335703cbbdd3f8876a8365b85e50 --- /dev/null +++ b/lib/ucoin/hdc/amendments/view.py @@ -0,0 +1,41 @@ +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# +# Authors: +# Caner Candan <caner@candan.fr>, http://caner.candan.fr +# + +from .. import HDC +from .. import logging + +logger = logging.getLogger("ucoin/hdc/amendments/view") + + +class View(HDC): + def __init__(self, amendment_id, server=None, port=None): + super().__init__('hdc/amendments/view/%s' % amendment_id, server, port) + + +class Self(View): + """Shows the raw data of the amendment [AMENDMENT_ID].""" + + def __get__(self, **kwargs): + return self.requests_get('/self', **kwargs).json() + + +class Signatures(View): + """GET the signatures of the Community listed in this amendment.""" + + def __get__(self, **kwargs): + return self.merkle_easy_parser('/signatures') diff --git a/lib/ucoin/hdc/coins/__init__.py b/lib/ucoin/hdc/coins/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..a4915137e5e78e0dd88b0d1b40816e3cdf8253c2 --- /dev/null +++ b/lib/ucoin/hdc/coins/__init__.py @@ -0,0 +1,36 @@ +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# +# Authors: +# Caner Candan <caner@candan.fr>, http://caner.candan.fr +# + +from .. import HDC +from .. import logging + +logger = logging.getLogger("ucoin/hdc/coins") + + +class Coins(HDC): + def __init__(self, pgp_fingerprint, server=None, port=None): + super().__init__('hdc/coins/%s' % pgp_fingerprint, server, port) + + +class List(Coins): + """GET a list of coins owned by [PGP_FINGERPRINT].""" + + def __get__(self, **kwargs): + return self.requests_get('/list', **kwargs).json() + +from . import view diff --git a/lib/ucoin/hdc/coins/view.py b/lib/ucoin/hdc/coins/view.py new file mode 100644 index 0000000000000000000000000000000000000000..8489bc015fd284290ad9c580b70dd458de814170 --- /dev/null +++ b/lib/ucoin/hdc/coins/view.py @@ -0,0 +1,42 @@ +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# +# Authors: +# Caner Candan <caner@candan.fr>, http://caner.candan.fr +# + +from .. import HDC +from .. import logging + +logger = logging.getLogger("ucoin/hdc/coins/view") + + +class Base(HDC): + def __init__(self, coin_id, server=None, port=None): + super().__init__('hdc/coins/view/%s' % coin_id, server, port) + + +class Owner(Base): + """GET a coin owner + justifying transaction if it exists.""" + + def __get__(self, **kwargs): + return self.requests_get('/owner', **kwargs).json() + + +class History(Base): + """GET a coin owner + justifying transaction + for each state a coin has gone trough.""" + + def __get__(self, **kwargs): + return self.requests_get('/history', **kwargs).json() diff --git a/lib/ucoin/hdc/transactions/__init__.py b/lib/ucoin/hdc/transactions/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..60ac2d0645e9de5d609eed80e4851b6c42dc4114 --- /dev/null +++ b/lib/ucoin/hdc/transactions/__init__.py @@ -0,0 +1,135 @@ +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# +# Authors: +# Caner Candan <caner@candan.fr>, http://caner.candan.fr +# + +from .. import HDC +from .. import logging + +logger = logging.getLogger("ucoin/hdc/transactions") + + +class Base(HDC): + def __init__(self, server=None, port=None): + super().__init__('hdc/transactions', server, port) + + +class Process(Base): + """POST a transaction.""" + + def __post__(self, **kwargs): + assert 'transaction' in kwargs + assert 'signature' in kwargs + + return self.requests_post('/process', **kwargs).json() + + +class Last(Base): + """GET the last received transaction.""" + + def __init__(self, count=None, server=None, port=None): + """ + Arguments: + - `count`: Integer indicating to + retrieve the last [COUNT] transactions. + """ + + super().__init__(server, port) + + self.count = count + + def __get__(self, **kwargs): + if not self.count: + return self.requests_get('/last', **kwargs).json() + + return self.requests_get('/last/%d' % self.count, **kwargs).json() + + +class Sender(Base): + """GET all the transactions sent by this sender and stored + by this node (should contain all transactions of the sender).""" + + def __init__(self, pgp_fingerprint, begin=None, end=None, server=None, port=None): + """ + Arguments: + - `pgp_fingerprint`: PGP fingerprint of the + key we want to see sent transactions. + - `begin`: integer value used by the + merkle parser to know when to begin requesting. + - `end`: integer value used by the + merkle parser to know when to end requesting. + """ + + super().__init__(server, port) + + self.pgp_fingerprint = pgp_fingerprint + self.begin = begin + self.end = end + + def __get__(self, **kwargs): + return self.merkle_easy_parser('/sender/%s' % self.pgp_fingerprint, + begin=self.begin, end=self.end) + + +class Recipient(Base): + """GET all the transactions received for + this recipient stored by this node.""" + + def __init__(self, pgp_fingerprint, begin=None, end=None, + server=None, port=None): + """ + Arguments: + - `pgp_fingerprint`: PGP fingerprint of the + key we want to see sent transactions. + - `begin`: integer value used by the + merkle parser to know when to begin requesting. + - `end`: integer value used by the + merkle parser to know when to end requesting. + """ + + super().__init__(server, port) + + self.pgp_fingerprint = pgp_fingerprint + self.begin = begin + self.end = end + + def __get__(self, **kwargs): + return self.merkle_easy_parser('/recipient/%s' % self.pgp_fingerprint, + begin=self.begin, end=self.end) + + +#TODO: Manager /refering/pgp_fingerprint/tx_number/ +class Refering(Base): + """GET all the transactions refering to source + transaction #[TX_NUMBER] issued by [PGP_FINGERPRINT].""" + + def __init__(self, pgp_fingerprint, tx_number, server=None, port=None): + """ + Arguments: + - `transaction_id`: The transaction unique identifier. + """ + + super().__init__(server, port) + + self.pgp_fingerprint = pgp_fingerprint + self.tx_number = tx_number + + def __get__(self, **kwargs): + return self.requests_get('/refering/%s/%d' + % (self.pgp_fingerprint, self.tx_number), + **kwargs).json() + +from . import sender diff --git a/lib/ucoin/hdc/transactions/sender/__init__.py b/lib/ucoin/hdc/transactions/sender/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..f7913c6e6b45754e479a8b0d0b925d0e35d0b2bc --- /dev/null +++ b/lib/ucoin/hdc/transactions/sender/__init__.py @@ -0,0 +1,80 @@ +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# +# Authors: +# Caner Candan <caner@candan.fr>, http://caner.candan.fr +# + +from ... import HDC +from ... import logging + +logger = logging.getLogger("ucoin/hdc/transactions/sender") + + +class Base(HDC): + """Get the last received transaction of a PGP key.""" + + def __init__(self, pgp_fingerprint, server=None, port=None): + """ + Arguments: + - `pgp_fingerprint`: PGP fingerprint of the key + we want to see sent transactions. + """ + + super().__init__('hdc/transactions/sender/%s' % pgp_fingerprint, + server, port) + + +class Last(Base): + """Get the last received transaction of a PGP key.""" + + def __init__(self, pgp_fingerprint, count=None, from_=None, + server=None, port=None): + """ + Arguments: + - `count`: Integer indicating to retrieve the last [COUNT] transactions + """ + + super().__init__(pgp_fingerprint, server, port) + + self.count = count + self.from_ = from_ + + def __get__(self, **kwargs): + if not self.count: + return self.requests_get('/last', **kwargs).json() + + if not self.from_: + return self.request_get('/last/%d' % self.count, **kwargs).json() + + return self.requests_get('/last/%d/%d' % (self.count, self.from_), + **kwargs).json() + + +class View(Base): + """GET the transaction of given TRANSACTION_ID.""" + + def __init__(self, pgp_fingerprint, tx_number, + server=None, port=None): + """ + Arguments: + - `count`: Integer indicating to retrieve the last [COUNT] transactions + """ + + super().__init__(pgp_fingerprint, server, port) + + self.tx_number = tx_number + + def __get__(self, **kwargs): + return self.requests_get('/view/%d' % self.tx_number, **kwargs).json() diff --git a/lib/ucoin/network/__init__.py b/lib/ucoin/network/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..b791fd978f5939573143003f28829a222b88f856 --- /dev/null +++ b/lib/ucoin/network/__init__.py @@ -0,0 +1,72 @@ +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# +# Authors: +# Caner Candan <caner@candan.fr>, http://caner.candan.fr +# + +from .. import API +from .. import logging + +logger = logging.getLogger("ucoin/ucg") + + +class Network(API): + def __init__(self, module='ucg', server=None, port=None): + super().__init__(module, server, port) + + +class Pubkey(Network): + """GET the public key of the peer.""" + + def __get__(self, **kwargs): + return self.requests_get('/pubkey', **kwargs).text + + +class Peering(Network): + """GET peering information about a peer.""" + + def __get__(self, **kwargs): + return self.requests_get('/peering', **kwargs).json() + + +class Wallet(Network): + """GET/POST Wallet entries.""" + + def __init__(self, pgp_fingerprint=None, server=None, port=None): + """ + Use the pgp fingerprint parameter in order to fit the result. + + Arguments: + - `pgp_fingerprint`: pgp fingerprint to use as a filter + """ + + super().__init__(server, port) + + self.pgp_fingerprint = pgp_fingerprint + + def __get__(self, **kwargs): + if not self.pgp_fingerprint: + return self.merkle_easy_parser('/wallet') + + return self.merkle_easy_parser('/wallet/%s' + % self.pgp_fingerprint).json() + + def __post__(self, **kwargs): + assert 'entry' in kwargs + assert 'signature' in kwargs + + return self.requests_post('/wallet', **kwargs) + +from . import peering diff --git a/lib/ucoin/network/peering/__init__.py b/lib/ucoin/network/peering/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..f0ddc3780268534557fc45f9b29d5abeca164cf6 --- /dev/null +++ b/lib/ucoin/network/peering/__init__.py @@ -0,0 +1,62 @@ +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# +# Authors: +# Caner Candan <caner@candan.fr>, http://caner.candan.fr +#networkfrom .. import UCG, logging + +from .. import Network +from .. import logging + +logger = logging.getLogger("ucoin/network/peering") + + +class Base(Network): + def __init__(self, server=None, port=None): + super().__init__('network/peering', server, port) + + +class Peers(Base): + """GET peering entries of every node inside the currency network.""" + + def __get__(self, **kwargs): + """creates a generator with one peering entry per iteration.""" + + return self.merkle_easy_parser('/peers') + + def __post__(self, **kwargs): + assert 'entry' in kwargs + assert 'signature' in kwargs + + return self.requests_post('/peers', **kwargs).json() + +class Forward(Base): + """POST a UCG forward document to this node in order to be sent back incoming transactions.""" + + def __post__(self, **kwargs): + assert 'forward' in kwargs + assert 'signature' in kwargs + + return self.requests_post('/forward', **kwargs).json() + +class Status(Base): + """POST a UCG status document to this node in order notify of its status.""" + + def __post__(self, **kwargs): + assert 'status' in kwargs + assert 'signature' in kwargs + + return self.requests_post('/status', **kwargs).json() + +from . import peers diff --git a/lib/ucoin/network/peering/peers.py b/lib/ucoin/network/peering/peers.py new file mode 100644 index 0000000000000000000000000000000000000000..f69d314aac00a532c5305ebbcc307e1005e4b8f3 --- /dev/null +++ b/lib/ucoin/network/peering/peers.py @@ -0,0 +1,79 @@ +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# +# Authors: +# Caner Candan <caner@candan.fr>, http://caner.candan.fr +# +from .. import Network +from .. import logging + +logger = logging.getLogger("ucoin/network/peering/peers") + +class Base(Network): + def __init__(self, server=None, port=None): + super().__init__('network/peering/peers', server, port) + +class Stream(Base): + """GET a list of peers this node is listening to/by for ANY incoming transaction.""" + + def __init__(self, request, pgp_fingerprint=None, server=None, port=None): + """ + Use the pgp fingerprint parameter in order to fit the result. + + Arguments: + - `request`: select the stream request + - `pgp_fingerprint`: pgp fingerprint to use as a filter + """ + + super().__init__(server, port) + + self.request = request + self.pgp_fingerprint = pgp_fingerprint + + +class UpStream(Stream): + """GET a list of peers this node is listening to for ANY incoming transaction.""" + + def __init__(self, pgp_fingerprint=None, server=None, port=None): + """ + Use the pgp fingerprint parameter in order to fit the result. + + Arguments: + - `pgp_fingerprint`: pgp fingerprint to use as a filter + """ + + super().__init__('upstream', pgp_fingerprint, server, port) + + def __get__(self, **kwargs): + """returns the corresponding peer list.""" + + return self.requests_get('/upstream/%s' % (self.request, self.pgp_fingerprint), **kwargs).json() + +class DownStream(Stream): + """GET a list of peers this node is listening by for ANY incoming transaction.""" + + def __init__(self, pgp_fingerprint=None, server=None, port=None): + """ + Use the pgp fingerprint parameter in order to fit the result. + + Arguments: + - `pgp_fingerprint`: pgp fingerprint to use as a filter + """ + + super().__init__('downstream', pgp_fingerprint, server, port) + + def __get__(self, **kwargs): + """returns the corresponding peer list.""" + + return self.requests_get('/downstream/%s' % (self.request, self.pgp_fingerprint), **kwargs).json() diff --git a/lib/ucoin/pks/__init__.py b/lib/ucoin/pks/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..809acbb7f82f2ee4508feee3f5c02627a829bf13 --- /dev/null +++ b/lib/ucoin/pks/__init__.py @@ -0,0 +1,60 @@ +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# +# Authors: +# Caner Candan <caner@candan.fr>, http://caner.candan.fr +# + +from .. import API +from .. import logging + +logger = logging.getLogger("ucoin/pks") + + +class PKS(API): + def __init__(self, module='pks', server=None, port=None): + super().__init__(module, server, port) + + +class Add(PKS): + """POST ASCII-armored OpenPGP certificates.""" + + def __post__(self, **kwargs): + assert 'keytext' in kwargs + assert 'keysign' in kwargs + + return self.requests_post('/add', **kwargs) + + +class Lookup(PKS): + """Allows to search for OpenPGP certificates, according to HKP draft.""" + + def __get__(self, **kwargs): + assert 'search' in kwargs + assert 'op' in kwargs + + r = self.requests_get('/lookup', **kwargs) + + if kwargs['op'] == 'get': return r.text + + return r.json() + + +class All(PKS): + """GET all the received public keys.""" + + def __get__(self, **kwargs): + """creates a generator with one public key per iteration.""" + + return self.merkle_easy_parser('/all') diff --git a/lib/ucoin/registry/__init__.py b/lib/ucoin/registry/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..c3793a484c8e17ca6346c79e31664293d66c5187 --- /dev/null +++ b/lib/ucoin/registry/__init__.py @@ -0,0 +1,38 @@ +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# + +from .. import API +from .. import logging + +logger = logging.getLogger("ucoin/registry") + + +class Registry(API): + def __init__(self, module='registry', server=None, port=None): + super().__init__(module, server, port) + + +class Parameters(Registry): + """GET parameters used by this community.""" + + def __get__(self, **kwargs): + return self.requests_get('/parameters', **kwargs).json() + + +class Amendment(Registry): + """GET parameters used by this community.""" + + def __get__(self, **kwargs): + return self.requests_get('/amendment', **kwargs).json() diff --git a/lib/ucoin/registry/amendment/__init__.py b/lib/ucoin/registry/amendment/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..7ef12380be969bba04806537db538e79e71eaa2c --- /dev/null +++ b/lib/ucoin/registry/amendment/__init__.py @@ -0,0 +1,37 @@ +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# + +from .. import Registry +from .. import logging + +logger = logging.getLogger("ucoin/registry") + + +class Base(Registry): + def __init__(self, server=None, port=None): + super().__init__('registry/amendment', server, port) + + +class Vote(Base): + + """GET the vote of current node for given amendment number + (both amendment + signature). + Such vote may be used by any node to broadcast the whole network.""" + + def __init__(self, am_number=None, server=None, port=None): + super().__init__(server, port) + + def __get__(self, **kwargs): + return self.merkle_easy_parser('/%s/vote' % self.am_number) diff --git a/lib/ucoin/registry/community/__init__.py b/lib/ucoin/registry/community/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..6c21d5e5a59fdd5323f0421d574efca5d93fb556 --- /dev/null +++ b/lib/ucoin/registry/community/__init__.py @@ -0,0 +1,38 @@ +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# + +from .. import Registry +from .. import logging + +logger = logging.getLogger("ucoin/registry") + + +class Base(Registry): + def __init__(self, server=None, port=None): + super().__init__('registry/community', server, port) + + +class Members(Base): + """GET the members present in the Community for this amendment.""" + + def __get__(self, **kwargs): + return self.merkle_easy_parser('/members') + + +class Voters(Base): + """GET the voters listed in this amendment.""" + + def __get__(self, **kwargs): + return self.merkle_easy_parser('/voters') diff --git a/lib/ucoin/registry/community/members.py b/lib/ucoin/registry/community/members.py new file mode 100644 index 0000000000000000000000000000000000000000..55de75cd892c72e4a5d3e7e6397cfa42a53d3501 --- /dev/null +++ b/lib/ucoin/registry/community/members.py @@ -0,0 +1,65 @@ +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# +# + +from .. import Registry +from .. import logging + +logger = logging.getLogger("ucoin/registry/community") + + +class Base(Registry): + def __init__(self, server=None, port=None): + super().__init__('hdc/registry/community', server, port) + + +class Current(Base): + """GET the last valid membership document for member pgp_fingerprint""" + + def __init__(self, pgp_fingerprint=None, server=None, port=None): + """ + Uses number to fit the result. + + Arguments: + - `number`: amendment number + """ + + super().__init__(server, port) + + self.pgp_fingerprint = pgp_fingerprint + + def __get__(self, **kwargs): + return self.requests_get('/members/%s/current' % self.pgp_fingerprint, + **kwargs).json() + + +class History(Base): + """GET the all received and stored membership documents""" + + def __init__(self, pgp_fingerprint=None, server=None, port=None): + """ + Uses number to fit the result. + + Arguments: + - `number`: amendment number + """ + + super().__init__(server, port) + + self.pgp_fingerprint = pgp_fingerprint + + def __get__(self, **kwargs): + return self.requests_get('/members/%s/history' % self.pgp_fingerprint, + **kwargs).json() diff --git a/lib/ucoin/registry/community/voters.py b/lib/ucoin/registry/community/voters.py new file mode 100644 index 0000000000000000000000000000000000000000..9f403502f137772bba3cf67b4b4039542efdc3bc --- /dev/null +++ b/lib/ucoin/registry/community/voters.py @@ -0,0 +1,65 @@ +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# +# + +from .. import Registry +from .. import logging + +logger = logging.getLogger("ucoin/registry/community") + + +class Base(Registry): + def __init__(self, server=None, port=None): + super().__init__('hdc/registry/community', server, port) + + +class Current(Base): + """GET the last valid membership document for member pgp_fingerprint""" + + def __init__(self, pgp_fingerprint=None, server=None, port=None): + """ + Uses number to fit the result. + + Arguments: + - `number`: amendment number + """ + + super().__init__(server, port) + + self.pgp_fingerprint = pgp_fingerprint + + def __get__(self, **kwargs): + return self.requests_get('/voters/%s/current' % self.pgp_fingerprint, + **kwargs).json() + + +class History(Base): + """GET the all received and stored membership documents""" + + def __init__(self, pgp_fingerprint=None, server=None, port=None): + """ + Uses number to fit the result. + + Arguments: + - `number`: amendment number + """ + + super().__init__(server, port) + + self.pgp_fingerprint = pgp_fingerprint + + def __get__(self, **kwargs): + return self.requests_get('/voters/%s/history' % self.pgp_fingerprint, + **kwargs).json() diff --git a/lib/ucoin/wrappers/__init__.py b/lib/ucoin/wrappers/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..55b11c19314f98130db7c060adc5969362954c84 --- /dev/null +++ b/lib/ucoin/wrappers/__init__.py @@ -0,0 +1,34 @@ +#!/usr/bin/env python3 +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# +# Authors: +# Caner Candan <caner@candan.fr>, http://caner.candan.fr +# + +import logging +from .. import pks, hdc, settings +import network + +logger = logging.getLogger("wrappers") + +class Wrapper: + def __init__(self, server=None, port=None): + self.server = server + self.port = port + + def __call__(self): + pass + +from . import transactions, coins diff --git a/lib/ucoin/wrappers/coins.py b/lib/ucoin/wrappers/coins.py new file mode 100644 index 0000000000000000000000000000000000000000..1793a0f98a5162b685690dab7127e98e526e36f3 --- /dev/null +++ b/lib/ucoin/wrappers/coins.py @@ -0,0 +1,86 @@ +#!/usr/bin/env python3 +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# +# Authors: +# Caner Candan <caner@candan.fr>, http://caner.candan.fr +# + +import logging +from . import Wrapper, pks, ucg, hdc, settings + +logger = logging.getLogger("coins") + +class Coins(Wrapper): + def __init__(self, pgp_fingerprint, server=None, port=None): + super().__init__(server, port) + + self.pgp_fingerprint = pgp_fingerprint + +class Get(Coins): + def __init__(self, pgp_fingerprint, values, server=None, port=None): + super().__init__(pgp_fingerprint, server, port) + + self.values = values + + def __call__(self): + __list = hdc.coins.List(self.pgp_fingerprint, self.server, self.port).get() + coins = {} + for c in __list['coins']: + for id in c['ids']: + n,b,p,t,i = id.split('-') + amount = int(b) * 10**int(p) + if amount not in coins: coins[amount] = [] + coins[amount].append({'issuer': c['issuer'], 'number': int(n), 'base': int(b), 'power': int(p), 'type': t, 'type_number': int(i), 'amount': amount}) + + issuers = {} + for v in self.values: + if v in coins and coins[v]: + c = coins[v].pop() + issuers[c['issuer']] = issuers.get(c['issuer']) or [] + issuers[c['issuer']].append(c) + else: + raise ValueError('You do not have enough coins of value (%d)' % v) + + res = '' + for i, issuer in enumerate(issuers): + if i > 0: res += ',' + res += issuer + for c in issuers[issuer]: + res += ':%(number)d' % c + + return res + +class List(Coins): + def __init__(self, pgp_fingerprint, limit=None, server=None, port=None): + super().__init__(pgp_fingerprint, server, port) + + self.limit = limit + + def __call__(self): + __list = hdc.coins.List(self.pgp_fingerprint, self.server, self.port).get() + coins = [] + __sum = 0 + + for c in __list['coins']: + for id in c['ids']: + n,b,p,t,i = id.split('-') + amount = int(b) * 10**int(p) + __dict = {'issuer': c['issuer'], 'number': int(n), 'base': int(b), 'power': int(p), 'type': t, 'type_number': int(i), 'amount': amount} + + if not self.limit or self.limit >= amount: + coins.append(__dict) + __sum += amount + + return __sum, coins diff --git a/lib/ucoin/wrappers/transactions.py b/lib/ucoin/wrappers/transactions.py new file mode 100644 index 0000000000000000000000000000000000000000..f4cc1f5bec5d154a2015ffe0de1fa8ad08599c52 --- /dev/null +++ b/lib/ucoin/wrappers/transactions.py @@ -0,0 +1,255 @@ +#!/usr/bin/env python3 +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# +# Authors: +# Caner Candan <caner@candan.fr>, http://caner.candan.fr +# + +import hashlib, logging +from . import Wrapper, pks, ucg, hdc, settings + +logger = logging.getLogger("transactions") + +class Transaction(Wrapper): + def __init__(self, type, pgp_fingerprint, message='', keyid=None, peering=None, server=None, port=None): + super().__init__(server, port) + self.keyid = keyid + self.pgp_fingerprint = pgp_fingerprint + self.message = message + self.type = type + self.error = None + self.peering = peering + + def __call__(self): + try: + last_tx = hdc.transactions.sender.Last(self.pgp_fingerprint, server=self.server, port=self.port).get() + except ValueError: + last_tx = None + + context_data = {} + context_data.update(settings) + context_data.update(self.peering if self.peering else ucg.Peering().get()) + context_data['version'] = 1 + context_data['number'] = 0 if not last_tx else last_tx['transaction']['number']+1 + context_data['previousHash'] = hashlib.sha1(("%(raw)s%(signature)s" % last_tx).encode('ascii')).hexdigest().upper() if last_tx else None + context_data['message'] = self.message + context_data['type'] = self.type + context_data['fingerprint'] = self.pgp_fingerprint + context_data.update(self.get_context_data()) + + tx = """\ +Version: %(version)d +Currency: %(currency)s +Sender: %(fingerprint)s +Number: %(number)d +""" % context_data + + if last_tx: tx += "PreviousHash: %(previousHash)s\n" % context_data + + try: + tx += self.get_message(context_data) + except ValueError as e: + self.error = e + return False + + tx += """\ +Comment: +%(message)s +""" % context_data + + tx = tx.replace("\n", "\r\n") + txs = settings['gpg'].sign(tx, keyid=self.keyid, detach=True) + + return self.process(tx, txs) + + def get_context_data(self): + return {} + + def get_message(self, context_data, tx=''): + return tx + + def get_error(self): + return self.error + + def process(self, tx, txs): + try: + hdc.transactions.Process(self.server, self.port).post(transaction=tx, signature=txs) + except ValueError as e: + self.error = str(e) + else: + return True + + return False + + def parse_coins(self, coins_message): + coins = [] + for coin in coins_message.split(','): + data = coin.split(':') + issuer, numbers = data[0], data[1:] + for number in numbers: + view = hdc.coins.View(issuer, int(number), self.server, self.port).get() + if view['owner'] != self.pgp_fingerprint: + raise ValueError('Trying to divide a coin you do not own (%s)' % view['id']) + coins.append(view) + return coins + + def get_coins_sum(self, coins): + __sum = 0 + for coin in coins: + base, power = coin['id'].split('-')[2:4] + __sum += int(base) * 10**int(power) + return __sum + + def check_coins_sum(self, __sum): + m = re.match(r'^(\d)(0*)$', str(__sum)) + if not m: raise ValueError('bad sum value %d' % __sum) + +class Transfer(Transaction): + def __init__(self, pgp_fingerprint, recipient, coins, message='', keyid=None, server=None, port=None): + super().__init__('TRANSFER', pgp_fingerprint, message, keyid=keyid, server=server, port=port) + + self.recipient = recipient + self.coins = coins + + def get_message(self, context_data, tx=''): + context_data['recipient'] = self.recipient + + tx += """\ +Recipient: %(recipient)s +Type: %(type)s +Coins: +""" % context_data + + for coin in self.coins.split(','): + data = coin.split(':') + issuer = data[0] + + for number in data[1:]: + context_data.update(hdc.coins.View(issuer, int(number), self.server, self.port).get()) + tx += '%(id)s, %(transaction)s\n' % context_data + return tx + + +class RawTransfer(Transaction): + def __init__(self, pgp_fingerprint, recipient, coins, message='', keyid=None, server=None, port=None): + super().__init__('TRANSFER', pgp_fingerprint, message, keyid=keyid, server=server, port=port) + + self.recipient = recipient + self.coins = coins + + def get_message(self, context_data, tx=''): + context_data['recipient'] = self.recipient + + tx += """\ +Recipient: %(recipient)s +Type: %(type)s +Coins: +""" % context_data + + for coin in self.coins: + data = coin.split('-') + context_data.update(hdc.coins.View(data[0], int(data[1]), self.server, self.port).get()) + tx += '%(id)s, %(transaction)s\n' % context_data + + return tx + + +class MonoTransaction(Transaction): + def get_next_coin_number(self, coins): + number = 0 + for c in coins: + candidate = int(c['id'].split('-')[1]) + if candidate > number: number = candidate + return number+1 + + def get_message(self, context_data, tx=''): + tx += """\ +Recipient: %(fingerprint)s +Type: %(type)s +Coins: +""" % context_data + + try: + last_issuance = hdc.transactions.sender.issuance.Last(self.pgp_fingerprint, self.server, self.port).get() + except ValueError: + last_issuance = None + + context_data['previous_idx'] = 0 if not last_issuance else self.get_next_coin_number(last_issuance['transaction']['coins']) + + tx += self.get_mono_message(context_data) + + return tx + + def get_mono_message(self, context_data, tx=''): + return tx + +class Issue(MonoTransaction): + def __init__(self, pgp_fingerprint, amendment, coins, message='', keyid=None, server=None, port=None): + super().__init__('ISSUANCE', pgp_fingerprint, message, keyid=keyid, server=server, port=port) + + self.amendment = amendment + self.coins = coins + + def get_mono_message(self, context_data, tx=''): + context_data['amendment'] = self.amendment + + for idx, coin in enumerate(self.coins): + context_data['idx'] = idx + context_data['previous_idx'] + context_data['base'], context_data['power'] = [int(x) for x in coin.split(',')] + tx += '%(fingerprint)s-%(idx)d-%(base)d-%(power)d-A-%(amendment)d\n' % context_data + + return tx + +class Fusion(MonoTransaction): + def __init__(self, pgp_fingerprint, coins, message='', keyid=None, server=None, port=None): + super().__init__('FUSION', pgp_fingerprint, message, keyid=keyid, server=server, port=port) + + self.coins = coins + + def get_mono_message(self, context_data, tx=''): + coins = self.parse_coins(self.coins) + self.check_coins_sum(self.get_coins_sum(coins)) + + context_data['base'], context_data['power'] = int(m.groups()[0]), len(m.groups()[1]) + tx += '%(fingerprint)s-%(previous_idx)d-%(base)d-%(power)d-F-%(number)d\n' % context_data + + for coin in coins: tx += '%(id)s, %(transaction)s\n' % coin + + return tx + +class Divide(MonoTransaction): + def __init__(self, pgp_fingerprint, old_coins, new_coins, message='', keyid=None, server=None, port=None): + super().__init__('DIVISION', pgp_fingerprint, message, keyid=keyid, server=server, port=port) + + self.old_coins = old_coins + self.new_coins = new_coins + + def get_mono_message(self, context_data, tx=''): + old_coins = self.parse_coins(self.old_coins) + old_coins_sum = self.get_coins_sum(old_coins) + + new_coins_sum = 0 + for idx, coin in enumerate(self.new_coins): + context_data['idx'] = idx + context_data['previous_idx'] + context_data['base'], context_data['power'] = [int(x) for x in coin.split(',')] + new_coins_sum += context_data['base'] * 10**context_data['power'] + tx += '%(fingerprint)s-%(idx)d-%(base)d-%(power)d-D-%(number)d\n' % context_data + + if old_coins_sum != new_coins_sum: + raise ValueError('Amount of old coins (%d) is not equal to new coins (%d)' % (old_coins_sum, new_coins_sum)) + + for coin in old_coins: tx += '%(id)s, %(transaction)s\n' % coin + + return tx