From e2ec8313dc190cc1bada8f16ec5b5c7414a71741 Mon Sep 17 00:00:00 2001 From: Pedro Ferreira Date: Sat, 5 Aug 2017 18:21:14 +0200 Subject: [PATCH] Refactor feed code --- ssb/feed.py | 66 ++++++++++++++++++++++++++++++++++-------- ssb/keys.py | 28 ------------------ ssb/tests/test_feed.py | 28 +++++++++--------- ssb/util.py | 7 ++++- 4 files changed, 74 insertions(+), 55 deletions(-) delete mode 100644 ssb/keys.py diff --git a/ssb/feed.py b/ssb/feed.py index cc6c289..25fa515 100644 --- a/ssb/feed.py +++ b/ssb/feed.py @@ -5,33 +5,69 @@ from hashlib import sha256 from simplejson import dumps, loads +from ssb.util import tag + OrderedMsg = namedtuple('OrderedMsg', ('previous', 'author', 'sequence', 'timestamp', 'hash', 'content')) +class NoPrivateKeyException(Exception): + pass + + def to_ordered(data): smsg = OrderedMsg(**data) return OrderedDict((k, getattr(smsg, k)) for k in smsg._fields) +class Feed(object): + def __init__(self, public_key): + self.public_key = public_key + + @property + def id(self): + return tag(self.public_key).decode('ascii') + + def sign(self): + raise NoPrivateKeyException('Cannot use remote identity to sign (no private key!)') + + +class LocalFeed(Feed): + def __init__(self, private_key): + self.private_key = private_key + + @property + def public_key(self): + return self.private_key.verify_key + + def sign(self, msg): + return self.private_key.sign(msg).signature + + class Message(object): - def __init__(self, keypair, content, timestamp=None, previous=None): - self.keypair = keypair + def __init__(self, feed, content, signature, sequence=1, timestamp=None, previous=None): + self.feed = feed self.content = content + self.signature = signature + self.previous = previous - self.sequence = (self.previous.sequence + 1) if self.previous else 1 + if self.previous: + self.sequence = self.previous.sequence + 1 + else: + self.sequence = sequence + self.timestamp = int(time.time() * 1000) if timestamp is None else timestamp @classmethod - def parse(cls, data, keypair): + def parse(cls, data, feed): obj = loads(data, object_pairs_hook=OrderedDict) - msg = cls(keypair, obj['content'], timestamp=obj['timestamp']) + msg = cls(feed, obj['content'], timestamp=obj['timestamp']) return msg, obj['signature'] def to_dict(self, add_signature=True): obj = to_ordered({ 'previous': self.previous.key if self.previous else None, - 'author': self.keypair.tag, + 'author': self.feed.id, 'sequence': self.sequence, 'timestamp': self.timestamp, 'hash': 'sha256', @@ -42,12 +78,6 @@ class Message(object): obj['signature'] = self.signature return obj - @property - def signature(self): - # ensure ordering of keys and indentation of 2 characters, like ssb-keys - data = dumps(self.to_dict(add_signature=False), indent=2) - return (b64encode(bytes(self.keypair.sign(data.encode('ascii')))) + b'.sig.ed25519').decode('ascii') - def verify(self, signature): return self.signature == signature @@ -59,3 +89,15 @@ class Message(object): @property def key(self): return '%' + self.hash + + +class LocalMessage(Message): + def __init__(self, feed, content, signature=None, sequence=1, timestamp=None, previous=None): + super(LocalMessage, self).__init__(feed, content, signature, sequence, timestamp, previous) + if signature is None: + self.signature = self.sign() + + def sign(self): + # ensure ordering of keys and indentation of 2 characters, like ssb-keys + data = dumps(self.to_dict(add_signature=False), indent=2) + return (b64encode(bytes(self.feed.sign(data.encode('ascii')))) + b'.sig.ed25519').decode('ascii') diff --git a/ssb/keys.py b/ssb/keys.py deleted file mode 100644 index fe29da2..0000000 --- a/ssb/keys.py +++ /dev/null @@ -1,28 +0,0 @@ -from base64 import b64encode - -from nacl.signing import SigningKey - - -def tag(key): - """Create tag from publick key.""" - return b'@' + b64encode(bytes(key)) + b'.ed25519' - - -class KeyPair(object): - def __init__(self, seed=None): - self.private_key = SigningKey.generate() if seed is None else SigningKey(seed) - self.public_key = self.private_key.verify_key - - @property - def tag(self): - return tag(self.public_key).decode('ascii') - - @property - def private_tag(self): - return tag(self.private_key) - - def sign(self, data): - return self.private_key.sign(data).signature - - def __repr__(self): - return "".format(self) diff --git a/ssb/tests/test_feed.py b/ssb/tests/test_feed.py index 9a06a47..c161a0e 100644 --- a/ssb/tests/test_feed.py +++ b/ssb/tests/test_feed.py @@ -2,29 +2,29 @@ from base64 import b64decode from collections import OrderedDict import pytest +from nacl.signing import SigningKey -from ssb.feed import Message -from ssb.keys import KeyPair +from ssb.feed import LocalMessage, LocalFeed @pytest.fixture() -def keypair(): +def feed(): secret = b64decode('Mz2qkNOP2K6upnqibWrR+z8pVUI1ReA1MLc7QMtF2qQ=') - return KeyPair(secret) + return LocalFeed(SigningKey(secret)) -def test_keypair(): +def test_feed(): secret = b64decode('Mz2qkNOP2K6upnqibWrR+z8pVUI1ReA1MLc7QMtF2qQ=') - kp = KeyPair(secret) - assert bytes(kp.private_key) == secret - assert bytes(kp.public_key) == b64decode('I/4cyN/jPBbDsikbHzAEvmaYlaJK33lW3UhWjNXjyrU=') - assert kp.tag == '@I/4cyN/jPBbDsikbHzAEvmaYlaJK33lW3UhWjNXjyrU=.ed25519' + feed = LocalFeed(SigningKey(secret)) + assert bytes(feed.private_key) == secret + assert bytes(feed.public_key) == b64decode('I/4cyN/jPBbDsikbHzAEvmaYlaJK33lW3UhWjNXjyrU=') + assert feed.id == '@I/4cyN/jPBbDsikbHzAEvmaYlaJK33lW3UhWjNXjyrU=.ed25519' -def test_message(keypair): - m1 = Message(keypair, OrderedDict([ +def test_message(feed): + m1 = LocalMessage(feed, OrderedDict([ ('type', 'about'), - ('about', keypair.tag), + ('about', feed.id), ('name', 'neo'), ('description', 'The Chosen One') ]), timestamp=1495706260190) @@ -35,9 +35,9 @@ def test_message(keypair): 'lPsQ9P10OgeyH6u0unFgiI2wV/RQ7Q2x2ebxnXYCzsJ055TBMXphRADTKhOMS2EkUxXQ9k3amj5fnWPudGxwBQ==.sig.ed25519' assert m1.key == '%xRDqws/TrQmOd4aEwZ32jdLhP873ZKjIgHlggPR0eoo=.sha256' - m2 = Message(keypair, OrderedDict([ + m2 = LocalMessage(feed, OrderedDict([ ('type', 'about'), - ('about', keypair.tag), + ('about', feed.id), ('name', 'morpheus'), ('description', 'Dude with big jaw') ]), previous=m1, timestamp=1495706447426) diff --git a/ssb/util.py b/ssb/util.py index 450c294..45aa995 100644 --- a/ssb/util.py +++ b/ssb/util.py @@ -1,6 +1,6 @@ import os import yaml -from base64 import b64decode +from base64 import b64decode, b64encode from nacl.signing import SigningKey @@ -9,6 +9,11 @@ class ConfigException(Exception): pass +def tag(key): + """Create tag from publick key.""" + return b'@' + b64encode(bytes(key)) + b'.ed25519' + + def load_ssb_secret(): """Load SSB keys from ~/.ssb""" with open(os.path.expanduser('~/.ssb/secret')) as f: