diff --git a/ssb/feed.py b/ssb/feed.py index 25fa515..aacf686 100644 --- a/ssb/feed.py +++ b/ssb/feed.py @@ -48,6 +48,9 @@ class Message(object): def __init__(self, feed, content, signature, sequence=1, timestamp=None, previous=None): self.feed = feed self.content = content + + if signature is None: + raise ValueError("signature can't be None") self.signature = signature self.previous = previous @@ -93,11 +96,23 @@ class Message(object): 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() + self.feed = feed + self.content = content - def sign(self): + self.previous = previous + 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 + + if signature is None: + self.signature = self._sign() + else: + self.signature = signature + + 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/tests/test_feed.py b/ssb/tests/test_feed.py index c161a0e..41037f8 100644 --- a/ssb/tests/test_feed.py +++ b/ssb/tests/test_feed.py @@ -2,18 +2,24 @@ from base64 import b64decode from collections import OrderedDict import pytest -from nacl.signing import SigningKey +from nacl.signing import SigningKey, VerifyKey -from ssb.feed import LocalMessage, LocalFeed +from ssb.feed import LocalMessage, LocalFeed, Feed, Message, NoPrivateKeyException @pytest.fixture() -def feed(): +def local_feed(): secret = b64decode('Mz2qkNOP2K6upnqibWrR+z8pVUI1ReA1MLc7QMtF2qQ=') return LocalFeed(SigningKey(secret)) -def test_feed(): +@pytest.fixture() +def remote_feed(): + public = b64decode('I/4cyN/jPBbDsikbHzAEvmaYlaJK33lW3UhWjNXjyrU=') + return Feed(VerifyKey(public)) + + +def test_local_feed(): secret = b64decode('Mz2qkNOP2K6upnqibWrR+z8pVUI1ReA1MLc7QMtF2qQ=') feed = LocalFeed(SigningKey(secret)) assert bytes(feed.private_key) == secret @@ -21,10 +27,17 @@ def test_feed(): assert feed.id == '@I/4cyN/jPBbDsikbHzAEvmaYlaJK33lW3UhWjNXjyrU=.ed25519' -def test_message(feed): - m1 = LocalMessage(feed, OrderedDict([ +def test_remote_feed(): + public = b64decode('I/4cyN/jPBbDsikbHzAEvmaYlaJK33lW3UhWjNXjyrU=') + feed = Feed(VerifyKey(public)) + assert bytes(feed.public_key) == public + assert feed.id == '@I/4cyN/jPBbDsikbHzAEvmaYlaJK33lW3UhWjNXjyrU=.ed25519' + + +def test_local_message(local_feed): + m1 = LocalMessage(local_feed, OrderedDict([ ('type', 'about'), - ('about', feed.id), + ('about', local_feed.id), ('name', 'neo'), ('description', 'The Chosen One') ]), timestamp=1495706260190) @@ -35,9 +48,9 @@ def test_message(feed): 'lPsQ9P10OgeyH6u0unFgiI2wV/RQ7Q2x2ebxnXYCzsJ055TBMXphRADTKhOMS2EkUxXQ9k3amj5fnWPudGxwBQ==.sig.ed25519' assert m1.key == '%xRDqws/TrQmOd4aEwZ32jdLhP873ZKjIgHlggPR0eoo=.sha256' - m2 = LocalMessage(feed, OrderedDict([ + m2 = LocalMessage(local_feed, OrderedDict([ ('type', 'about'), - ('about', feed.id), + ('about', local_feed.id), ('name', 'morpheus'), ('description', 'Dude with big jaw') ]), previous=m1, timestamp=1495706447426) @@ -47,3 +60,42 @@ def test_message(feed): assert m2.signature == \ '3SY85LX6/ppOfP4SbfwZbKfd6DccbLRiB13pwpzbSK0nU52OEJxOqcJ2Uensr6RkrWztWLIq90sNOn1zRAoOAw==.sig.ed25519' assert m2.key == '%nx13uks5GUwuKJC49PfYGMS/1pgGTtwwdWT7kbVaroM=.sha256' + + +def test_remote_message(remote_feed): + signature = 'lPsQ9P10OgeyH6u0unFgiI2wV/RQ7Q2x2ebxnXYCzsJ055TBMXphRADTKhOMS2EkUxXQ9k3amj5fnWPudGxwBQ==.sig.ed25519' + m1 = Message(remote_feed, OrderedDict([ + ('type', 'about'), + ('about', remote_feed.id), + ('name', 'neo'), + ('description', 'The Chosen One') + ]), signature, timestamp=1495706260190) + assert m1.timestamp == 1495706260190 + assert m1.previous is None + assert m1.sequence == 1 + assert m1.signature == signature + assert m1.key == '%xRDqws/TrQmOd4aEwZ32jdLhP873ZKjIgHlggPR0eoo=.sha256' + + signature = '3SY85LX6/ppOfP4SbfwZbKfd6DccbLRiB13pwpzbSK0nU52OEJxOqcJ2Uensr6RkrWztWLIq90sNOn1zRAoOAw==.sig.ed25519' + m2 = Message(remote_feed, OrderedDict([ + ('type', 'about'), + ('about', remote_feed.id), + ('name', 'morpheus'), + ('description', 'Dude with big jaw') + ]), signature, previous=m1, timestamp=1495706447426) + assert m2.timestamp == 1495706447426 + assert m2.previous is m1 + assert m2.sequence == 2 + assert m2.signature == signature + m2.verify(signature) + assert m2.key == '%nx13uks5GUwuKJC49PfYGMS/1pgGTtwwdWT7kbVaroM=.sha256' + + +def test_remote_no_signature(remote_feed): + with pytest.raises(ValueError): + Message(remote_feed, OrderedDict([ + ('type', 'about'), + ('about', remote_feed.id), + ('name', 'neo'), + ('description', 'The Chosen One') + ]), None, timestamp=1495706260190)