Refactor feed code
This commit is contained in:
parent
837fd5e38b
commit
e2ec8313dc
66
ssb/feed.py
66
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')
|
||||
|
28
ssb/keys.py
28
ssb/keys.py
@ -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 "<KeyPair {0.tag}>".format(self)
|
@ -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)
|
||||
|
@ -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:
|
||||
|
Loading…
Reference in New Issue
Block a user