diff --git a/_ucoinpy_test/documents/test_block.py b/_ucoinpy_test/documents/test_block.py new file mode 100644 index 0000000000000000000000000000000000000000..0dcc8d744a31032e81cc95f72d33fc97b915f4ba --- /dev/null +++ b/_ucoinpy_test/documents/test_block.py @@ -0,0 +1,42 @@ +''' +Created on 12 déc. 2014 + +@author: inso +''' +import pytest +from ucoinpy.documents.block import Block +from mock import Mock + + +raw_block = "Version: 1\nType: \ +Block\nCurrency: zeta_brouzouf\n\ +Nonce: 45079\nNumber: 15\nPoWMin: 4\n\ +Time: 1418083330\nMedianTime: 1418080208\n\ +Issuer: HnFcSms8jzwngtVomTTnzudZx7SHUQY8sVE1y8yBmULk\n\ +PreviousHash: 0000E73C340601ACA1AD5AAA5B5E56B03E178EF8\n\ +PreviousIssuer: HnFcSms8jzwngtVomTTnzudZx7SHUQY8sVE1y8yBmULk\n\ +MembersCount: 4\nIdentities:\nJoiners:\nActives:\nLeavers:\n\ +Excluded:\nCertifications:\nTransactions:\n" + + +class Test_Block: + def test_fromraw(self): + block = Block.from_raw(raw_block) + assert block.version == 1 + assert block.currency == "zeta_brouzouf" + assert block.noonce == 45079 + assert block.number == 15 + assert block.powmin == 4 + assert block.time == 1418083330 + assert block.mediantime == 1418080208 + assert block.issuer == "HnFcSms8jzwngtVomTTnzudZx7SHUQY8sVE1y8yBmULk" + assert block.previoushash == "0000E73C340601ACA1AD5AAA5B5E56B03E178EF8" + assert block.previousissuer == "0000E73C340601ACA1AD5AAA5B5E56B03E178EF8" + assert block.memberscount == 4 + assert block.identities == [] + assert block.joiners == [] + assert block.actives == [] + assert block.leavers == [] + assert block.excluded == [] + assert block.certifications == [] + assert block.transactions == [] diff --git a/_ucoinpy_test/documents/test_certification.py b/_ucoinpy_test/documents/test_certification.py new file mode 100644 index 0000000000000000000000000000000000000000..0d53c3b3c1cf08ac94a21a0b000ba79d158d060f --- /dev/null +++ b/_ucoinpy_test/documents/test_certification.py @@ -0,0 +1,31 @@ +''' +Created on 6 déc. 2014 + +@author: inso +''' + +import pytest +from ucoinpy.documents.certification import SelfCertification +from ucoinpy.key import Base58Encoder +from mock import Mock + +from nacl.signing import SigningKey + +uid = "lolcat" +timestamp = 1409990782.24 +correct_certification = """UID:lolcat +META:TS:1409990782 +J3G9oM5AKYZNLAB5Wx499w61NuUoS57JVccTShUbGpCMjCqj9yXXqNq7dyZpDWA6BxipsiaMZhujMeBfCznzyci +""" + +key = SigningKey() + + +class Test_SelfCertification: + ''' + classdocs + ''' + + def test_certification(self): + cert = SelfCertification(timestamp, uid) + assert cert.signed(key) == correct_certification diff --git a/ucoinpy/api/bma/blockchain/__init__.py b/ucoinpy/api/bma/blockchain/__init__.py index e4e02c458a3b9b8d3bda60b9408c36261165253c..038902651481901e8b37409d5b0b65e76736217c 100644 --- a/ucoinpy/api/bma/blockchain/__init__.py +++ b/ucoinpy/api/bma/blockchain/__init__.py @@ -93,3 +93,59 @@ class Hardship(Blockchain): def __get__(self, **kwargs): assert self.fingerprint is not None return self.requests_get('/hardship/%s' % self.fingerprint.upper(), **kwargs).json() + + +class Newcomers(Blockchain): + """GET, return block numbers containing newcomers.""" + + def __get__(self, **kwargs): + return self.requests_get('/with/newcomers', **kwargs).json() + + +class Certifications(Blockchain): + """GET, return block numbers containing certifications.""" + + def __get__(self, **kwargs): + return self.requests_get('/with/certs', **kwargs).json() + + +class Joiners(Blockchain): + """GET, return block numbers containing joiners.""" + + def __get__(self, **kwargs): + return self.requests_get('/with/joiners', **kwargs).json() + + +class Actives(Blockchain): + """GET, return block numbers containing actives.""" + + def __get__(self, **kwargs): + return self.requests_get('/with/actives', **kwargs).json() + + +class Leavers(Blockchain): + """GET, return block numbers containing leavers.""" + + def __get__(self, **kwargs): + return self.requests_get('/with/leavers', **kwargs).json() + + +class Excluded(Blockchain): + """GET, return block numbers containing excluded.""" + + def __get__(self, **kwargs): + return self.requests_get('/with/excluded', **kwargs).json() + + +class UD(Blockchain): + """GET, return block numbers containing universal dividend.""" + + def __get__(self, **kwargs): + return self.requests_get('/with/ud', **kwargs).json() + + +class TX(Blockchain): + """GET, return block numbers containing transactions.""" + + def __get__(self, **kwargs): + return self.requests_get('/with/tx', **kwargs).json() diff --git a/ucoinpy/api/bma/wot/__init__.py b/ucoinpy/api/bma/wot/__init__.py index aa1ca0de576a79ab4f024c7642388342147065d3..3e1c694a7ef51a99703fcbee009cc669f46b00e6 100644 --- a/ucoinpy/api/bma/wot/__init__.py +++ b/ucoinpy/api/bma/wot/__init__.py @@ -49,3 +49,41 @@ class Lookup(WOT): assert self.search is not None return self.requests_get('/lookup/%s' % self.search, **kwargs).json() + + +class CertifiersOf(WOT): + """GET Certification data over a member.""" + + def __init__(self, connection_handler, search, module='wot'): + super(WOT, self).__init__(connection_handler, module) + + self.search = search + + def __get__(self, **kwargs): + assert self.search is not None + + return self.requests_get('/certifiers-of/%s' % self.search, **kwargs).json() + + +class CertifiedBy(WOT): + """GET Certification data from a member.""" + + def __init__(self, connection_handler, search, module='wot'): + super(WOT, self).__init__(connection_handler, module) + + self.search = search + + def __get__(self, **kwargs): + assert self.search is not None + + return self.requests_get('/certified-by/%s' % self.search, **kwargs).json() + + +class Members(WOT): + """GET List all current members of the Web of Trust.""" + + def __init__(self, connection_handler, module='wot'): + super(WOT, self).__init__(connection_handler, module) + + def __get__(self, **kwargs): + return self.requests_get('/members', **kwargs).json() diff --git a/ucoinpy/documents/__init__.py b/ucoinpy/documents/__init__.py index 0fe4cc12ddd034b516ba535eee18d8414a236cae..4e993849897a27ced08c290760f92a05f5ee28d1 100644 --- a/ucoinpy/documents/__init__.py +++ b/ucoinpy/documents/__init__.py @@ -4,19 +4,12 @@ Created on 3 déc. 2014 @author: inso ''' import base58 -import time +import re from ..key import Base58Encoder class Document: + RE_VERSION = re.compile("Version: ([0-9]+)\n") + def __init__(self, version): self.version = version - - def content(self): - return "" - - def sign(self, key): - return key.sign(self.content(), encoder=Base58Encoder) - - def signed(self, key): - return "{0}\n{1}\n".format(self.content(), self.sign(key)) diff --git a/ucoinpy/documents/block.py b/ucoinpy/documents/block.py index 751ae9de50c8742911c62c072642cc7ca85e7803..55bb26a2706892b7ae6062c4ffd6962b0d89710a 100644 --- a/ucoinpy/documents/block.py +++ b/ucoinpy/documents/block.py @@ -6,6 +6,11 @@ Created on 2 déc. 2014 from .. import PROTOCOL_VERSION from . import Document +from .certification import SelfCertification, Certification +from .membership import Membership +from .transaction import Transaction + +import re class Block(Document): @@ -48,14 +53,36 @@ COMPACT_TRANSACTION BOTTOM_SIGNATURE ''' - def __init__(self, currency, noonce, number, powmin, time, + re_type = re.compile("Type: (Block)\n") + re_noonce = re.compile("Nonce: ([0-9]+)\n") + re_number = re.compile("Number: ([0-9]+)\n") + re_powmin = re.compile("PoWMin: ([0-9]+)\n") + re_time = re.compile("Time: ([0-9]+)\n") + re_mediantime = re.compile("MedianTime: ([0-9]+)\n") + re_universaldividend = re.compile("UniversalDividend: ([0-9]+)\n") + re_issuer = re.compile("Issuer: ([1-9A-Za-z][^OIl]{43,45})\n") + re_previoushash = re.compile("PreviousHash: ([0-9a-fA-F]{5,40})\n") + re_previousissuer = re.compile("PreviousIssuer: ([1-9A-Za-z][^OIl]{43,45})\n") + re_parameters = re.compile("Parameters: ([0-9]+\.[0-9]+)(:[0-9]+){10}\n") + re_memberscount = re.compile("MembersCount: ([0-9]+)\n") + re_identities = re.compile("Identities:\n") + re_joiners = re.compile("Joiners:\n") + re_actives = re.compile("Actives:\n") + re_leavers = re.compile("Leavers:\n") + re_excluded = re.compile("Excluded:\n") + re_certifications = re.compile("Certifications:\n") + re_transactions = re.compile("Transactions:\n") + re_sign = re.compile("([A-Za-z0-9+/]+)") + + def __init__(self, version, currency, noonce, number, powmin, time, mediantime, ud, issuer, prev_hash, prev_issuer, parameters, members_count, identities, joiners, actives, leavers, excluded, certifications, - transactions): + transactions, sign): ''' Constructor ''' + super(version) self.currency = currency self.noonce = noonce self.number = number @@ -75,69 +102,120 @@ BOTTOM_SIGNATURE self.excluded = excluded self.certifications = certifications self.transactions = transactions - + self.sign = sign + @classmethod def from_raw(cls, raw): #TODO : Parsing lines = raw.splitlines(True) - + n = 0 - version_re = re.compile("Version: ([0-9]+)\n") - version = version_re.match(lines[n]) - n = 1 - currency_re = re.compile("Currency: ([0-9a-zA-Z_\-]+)\n" - currency = currency_re.match(lines[n]) - - n = 2 - noonce_re = re.compile("Nonce: ([0-9]+)\n") - noonce = nonce_re.match(lines[n]) - - n = 3 - number_re = re.compile("Number: ([0-9]+)\n") - number = number_re.match(lines[n]) - - n = 4 - powmin_re = re.compile("PoWMin: ([0-9]+)\n") - powmin = powmin.match(lines[n]) - - n = 5 - time_re = re.compile("Time: ([0-9]+)\n") - time = time.match(lines[n]) - - n = 6 - mediantime_re = re.compile("MedianTime: ([0-9]+)\n") - mediantime = mediantime_re.match(line[n]) - - n = 7 - ud_re = re.compile("UniversalDividend: ([0-9]+)\n") - ud = ud_re.match(line[n]) - - n = 8 - issuer_re = re.compile("Issuer: ([1-9A-Za-z][^OIl]{43,45})\n") - issuer = issuer_re.match(line[n]) - - n = 9 - previoushash_re = re.compile("PreviousHash: ([0-9a-fA-F]{5,40})\n") - prev_hash = previoushash_re.match(line[n]) - - n = 10 - previousissuer_re = re.compile("PreviousIssuer: ([1-9A-Za-z][^OIl]{43,45})\n") - prev_issuer = previousissuer_re.match(line[n]) - - n = 11 - parameters = "" - members_count = "" - identities = "" - joiners = "" - actives = "" - leavers = "" - excluded = "" - certifications = "" - transactions = "" - - return cls([]) - - def content(self): + + version = Block.re_version.match(lines[n]).group(1) + n = n + 1 + + type = Block.re_type.match(lines[n]).group(1) + n = n + 1 + + currency = Block.re_currency.match(lines[n]).group(1) + n = n + 1 + + noonce = int(Block.re_noonce.match(lines[n])).group(1) + n = n + 1 + + number = int(Block.re_number.match(lines[n])).group(1) + n = n + 1 + + powmin = int(Block.re_powmin.match(lines[n])).group(1) + n = n + 1 + + time = int(Block.re_time.match(lines[n])).group(1) + n = n + 1 + + mediantime = int(Block.re_mediantime.match(lines[n])).group(1) + n = n + 1 + + ud = Block.re_universaldividend.match(lines[n]).group(1) + if ud is not None: + ud = int(ud) + n = n + 1 + + issuer = Block.re_issuer.match(lines[n]).group(1) + n = n + 1 + + prev_hash = Block.re_previoushash.match(lines[n]).group(1) + n = n + 1() + + prev_issuer = Block.re_previousissuer.match(lines[n]).group(1) + n = n + 1 + + if number == 0: + parameters = Block.re_parameters.match(lines[n]).group(1) + n = n + 1 + + members_count = int(Block.re_memberscount.match(lines[n])).group(1) + n = n + 1 + + identities = [] + joiners = [] + actives = [] + leavers = [] + excluded = [] + certifications = [] + transactions = [] + + if Block.re_identities.match(lines[n]) is not None: + while Block.re_joiners.match(lines[n]) is None: + selfcert = SelfCertification.from_inline(version, lines[n]) + identities.append(selfcert) + lines = lines + 1 + + if Block.re_joiners.match(lines[n]) is not None: + while Block.re_actives.match(lines[n]) is None: + membership = Membership.from_inline(version, currency, "IN", lines[n]) + joiners.append(membership) + lines = lines + 1 + + if Block.re_actives.match(lines[n]) is not None: + while Block.re_leavers.match(lines[n]) is None: + membership = Membership.from_inline(version, currency, "IN", lines[n]) + actives.append(membership) + lines = lines + 1 + + if Block.re_leavers.match(lines[n]) is not None: + while Block.re_excluded.match(lines[n]) is None: + membership = Membership.from_inline(version, currency, "OUT", lines[n]) + leavers.append(membership) + lines = lines + 1 + + if Block.re_excluded.match(lines[n]) is not None: + while Block.re_certifications.match(lines[n]) is None: + membership = Membership.from_inline(version, currency, "OUT", lines[n]) + excluded.append(membership) + lines = lines + 1 + + if Block.re_certifications.match(lines[n]) is not None: + while Block.re_transactions.match(lines[n]) is None: + certification = Certification.from_inline(version, lines[n]) + certifications.append(certification) + lines = lines + 1 + + if Block.re_transactions.match(lines[n]) is not None: + while Block.re_transactions.match(lines[n]) is None: + transaction = Transaction.from_compact(version, lines[n]) + transactions.append(transaction) + lines = lines + 1 + + sign = Block.re_sign.match(lines[n]) + n = n + 1 + + return cls(version, currency, noonce, number, powmin, time, + mediantime, ud, issuer, prev_hash, prev_issuer, + parameters, members_count, identities, joiners, + actives, leavers, excluded, certifications, + transactions, sign) + + def raw(self): doc = """ Version: {0} Type: Block @@ -193,3 +271,5 @@ Identities:""".format(PROTOCOL_VERSION, doc += "Transactions:\n" for transaction in self.transactions: doc += "{0}\n".format(transaction.inline()) + + doc += self.sign diff --git a/ucoinpy/documents/certification.py b/ucoinpy/documents/certification.py index 2ad28da18257094df21f47af4e33d99e12d38f11..b9f821eabd43cf0195c8096d3f4f5da78d93eba0 100644 --- a/ucoinpy/documents/certification.py +++ b/ucoinpy/documents/certification.py @@ -3,6 +3,8 @@ Created on 2 déc. 2014 @author: inso ''' +import re + from . import Document @@ -11,45 +13,72 @@ class SelfCertification(Document): A document discribing a self certification. ''' - def __init__(self, identifier): + re_inline = re.compile("([1-9A-Za-z][^OIl]{43,45}):([A-Za-z0-9+/]+):([0-9]+):([^\n]+)\n") + + def __init__(self, version, pubkey, ts, identifier, sign): + super(version) + self.pubkey = pubkey + self.timestamp = ts self.identifier = identifier - self.timestamp = timestamp - + self.sign = sign + + @classmethod + def from_inline(cls, version, inline): + selfcert_data = SelfCertification.re_inline.match(inline) + pubkey = selfcert_data.group(1) + sign = selfcert_data.group(2) + ts = selfcert_data.group(3) + identifier = selfcert_data.group(4) + return cls(version, pubkey, ts, identifier, sign) + @classmethod def from_raw(cls, raw): #TODO : Parsing return cls() - def ts(self): + def ts(self): return "META:TS:{0}".format(self.timestamp) - + def uid(self): return "UID:{0}".format(self.identifier) - def content(self): + def raw(self): return "{0}\n{1}".format(self.uid(), self.ts()) class Certification(Document): ''' - classdocs + A document describing a certification. ''' - def __init__(self, selfcert, blockid): + re_inline = re.compile("([1-9A-Za-z][^OIl]{43,45}):\ + ([A-Za-z0-9+/]+)(==)?:([0-9]+):([0-9a-fA-F]{5,40}):\ + ([0-9]+):([^\n]+)\n") + + def __init__(self, version, pubkey_from, pubkey_to, + blockhash, blocknumber, sign): ''' Constructor ''' - self.selfcert = selfcert - self.blockid = blockid + super(version) + self.pubkey_from = pubkey_from + self.pubkey_to = pubkey_to + self.blockhash = blockhash + self.blocknumber = blocknumber + self.sign = sign - @classmethod - def from_raw(cls, raw): - #TODO : Parsing - return cls() + def from_inline(cls, version, blockhash, inline): + cert_data = Certification.re_inline.match(inline) + pubkey_from = cert_data.group(1) + pubkey_to = cert_data.group(2) + blocknumber = cert_data.group(3) + sign = cert_data.group(4) + return cls(version, pubkey_from, pubkey_to, + blockhash, blocknumber, sign) def ts(self): - return "META:TS:{0}".format(self.blockid) + return "META:TS:{0}-{1}".format(self.blockhash, self.blocknumber) - def content(self): - return "{0}\n{1}".format(self.selfcert.content(), self.ts()) + def raw(self, selfcert): + return "{0}\n{1}".format(selfcert.content(), self.ts()) diff --git a/ucoinpy/documents/membership.py b/ucoinpy/documents/membership.py index 05ddb7df53b4ccc23df7279a6fe05afbcf19ef23..49eef154f190caebce68d626c0caa6644831eada 100644 --- a/ucoinpy/documents/membership.py +++ b/ucoinpy/documents/membership.py @@ -4,9 +4,12 @@ Created on 2 déc. 2014 @author: inso ''' from .. import PROTOCOL_VERSION +from . import Document +import re -class Membership(object): + +class Membership(Document): ''' This is a utility class to generate membership documents : Version: VERSION @@ -19,11 +22,16 @@ class Membership(object): CertTS: CERTIFICATION_TS ''' - def __init__(self, currency, issuer, block_number, block_hash, - membership_type, userid, cert_ts): + # PUBLIC_KEY:SIGNATURE:NUMBER:HASH:TIMESTAMP:USER_ID + re_inline = re.compile("([1-9A-Za-z][^OIl]{43,45}):([A-Za-z0-9+/]+):\ + ([0-9]+):([0-9a-fA-F]{5,40}):([0-9]+):([^\n]+)\n") + + def __init__(self, version, currency, issuer, block_number, block_hash, + membership_type, userid, cert_ts, sign): ''' Constructor ''' + super(version) self.currency = currency self.issuer = issuer self.block_number = block_number @@ -31,15 +39,25 @@ class Membership(object): self.membership_type = membership_type self.userid = userid self.cert_ts = cert_ts + self.sign = sign + + @classmethod + def from_inline(cls, version, currency, type, inline): + data = Membership.re_inline.match(inline) + issuer = data.group(1) + sign = data.group(2) + block_number = data.group(3) + block_hash = data.group(4) + cert_ts = data.group(5) + userid = data.group(6) + return cls(version, currency, issuer, block_number, + block_hash, type, userid, cert_ts, sign) - @classmethod def from_raw(cls, raw): #TODO : Parsing return cls() - - def content(self): return """ Version: {0} @@ -59,7 +77,7 @@ CertTS: {7}""".format(PROTOCOL_VERSION, def inline(self): return "{0}:{1}:{2}:{3}".format(self.issuer, - self.sign(), + self.sign, self.block_number, self.block_hash, self.cert_ts) diff --git a/ucoinpy/documents/peer.py b/ucoinpy/documents/peer.py index d696665bd47f2c89689476f296e9e26a15ead25d..0a387f79e884b7d62c32f80cf2a13b8217bf06b9 100644 --- a/ucoinpy/documents/peer.py +++ b/ucoinpy/documents/peer.py @@ -29,7 +29,7 @@ class Peer(Document): self.pubkey = pubkey self.blockid = blockid self.endpoints = endpoints - + @classmethod def from_raw(cls, raw): #TODO : Parsing @@ -59,22 +59,22 @@ class Endpoint(): def from_inline(inline): for api in MANAGED_API: if (inline.startswith(api)): - return Endpoint.parse_line(inline, api) - - @staticmethod - def parse_line(self, inline, api): - if (api == "BASIC_MERKLED_API"): - bma_endpoints = re.compile('^BASIC_MERKLED_API( ([a-z_][a-z0-9-_.]+))?( ([0-9.]+))?( ([0-9a-f:]+))?( ([0-9]+))$') - m = bma_endpoints.match(inline) - server = m.group(2) - ipv4 = m.group(4) - ipv6 = m.group(6) - port = int(m.group(8)) - return BMAEndpoint(server, ipv4, ipv6, port) - return None + if (api == "BASIC_MERKLED_API"): + return BMAEndpoint.from_inline(inline) class BMAEndpoint(Endpoint): + re_inline = re.compile('^BASIC_MERKLED_API( ([a-z_][a-z0-9-_.]+))?( ([0-9.]+))?( ([0-9a-f:]+))?( ([0-9]+))$') + + @classmethod + def from_inline(cls, inline): + m = BMAEndpoint.re_inline.match(inline) + server = m.group(2) + ipv4 = m.group(4) + ipv6 = m.group(6) + port = int(m.group(8)) + return cls(server, ipv4, ipv6, port) + def __init__(self, server, ipv4, ipv6, port): self.server = server self.ipv4 = ipv4 diff --git a/ucoinpy/documents/transaction.py b/ucoinpy/documents/transaction.py index beaf476469305ad99bbc9f2edc88c06c3cdb49dd..ee5a824bef806e643243815f7bbaaca0157f5b1f 100644 --- a/ucoinpy/documents/transaction.py +++ b/ucoinpy/documents/transaction.py @@ -37,6 +37,10 @@ SIGNATURES self.outputs = outputs self.comment = comment + @classmethod + def from_compact(cls, compact): + return None + @classmethod def from_raw(cls, raw): #TODO : Parsing diff --git a/ucoinpy/key/__init__.py b/ucoinpy/key/__init__.py index 49a5fec73d28ef80aef5b07e2663c970c0fd80d4..1733e547b828b22f208c84d666d51ecbcc8093f7 100644 --- a/ucoinpy/key/__init__.py +++ b/ucoinpy/key/__init__.py @@ -5,6 +5,25 @@ Ucoin public and private keys ''' import base58 +import base64 +import scrypt +from nacl.signing import SigningKey as NaclSigningKey + + +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) class Base58Encoder(object):