refactor and add/use error type
This commit is contained in:
parent
81329e55ee
commit
575d9a353e
15 changed files with 725 additions and 455 deletions
221
src/atoms.rs
221
src/atoms.rs
|
@ -1,221 +0,0 @@
|
|||
use std::{fmt::{Debug, Display}, str::FromStr};
|
||||
use ed25519_dalek::{Signer, Verifier};
|
||||
use rand::{distributions::Alphanumeric, Rng};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use base64::{Engine, engine::general_purpose::URL_SAFE_NO_PAD as b64engine};
|
||||
|
||||
#[derive(Hash, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
|
||||
pub struct RoomId(String);
|
||||
|
||||
#[derive(Hash, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
|
||||
#[serde(try_from = "&str", into = "String")]
|
||||
pub struct EventId(EventHash);
|
||||
|
||||
#[derive(Hash, Clone, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub struct ActorId(ed25519::ComponentBytes);
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct ActorSecret(ed25519::ComponentBytes);
|
||||
|
||||
#[derive(Clone, PartialEq, Eq)]
|
||||
pub struct ActorSignature(ed25519::Signature);
|
||||
|
||||
// #[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
|
||||
// enum ActorIdType {
|
||||
// Debug(String),
|
||||
// Ed25519(ed25519::ComponentBytes),
|
||||
// }
|
||||
|
||||
// #[derive(Debug, Clone, PartialEq, Eq)]
|
||||
// enum ActorSecretType {
|
||||
// Debug(String),
|
||||
// Ed25519(ed25519::ComponentBytes),
|
||||
// }
|
||||
|
||||
// #[derive(Clone, PartialEq, Eq)]
|
||||
// enum ActorSignatureType {
|
||||
// Debug(String),
|
||||
// Ed25519(ed25519::Signature),
|
||||
// }
|
||||
|
||||
#[derive(Debug, Hash, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
|
||||
enum EventHash {
|
||||
Debug(String),
|
||||
Sha2(Vec<u8>),
|
||||
}
|
||||
|
||||
impl RoomId {
|
||||
// maybe use the initial create event hash/id as the room id?
|
||||
// ie. !foobar refers to the room while $foobar refers to the event
|
||||
pub fn random() -> Self {
|
||||
let rng = rand::thread_rng()
|
||||
.sample_iter(Alphanumeric)
|
||||
.map(char::from)
|
||||
.take(12)
|
||||
.collect();
|
||||
Self(rng)
|
||||
}
|
||||
}
|
||||
|
||||
impl ActorId {
|
||||
pub fn new_pair() -> (ActorId, ActorSecret) {
|
||||
use rand::rngs::OsRng;
|
||||
use ed25519_dalek::SigningKey;
|
||||
|
||||
let mut csprng = OsRng;
|
||||
let signing_key: SigningKey = SigningKey::generate(&mut csprng);
|
||||
signing_key.verifying_key();
|
||||
let bytes = signing_key.to_keypair_bytes();
|
||||
let id_bytes = ed25519::ComponentBytes::try_from(&bytes[0..ed25519_dalek::SECRET_KEY_LENGTH]).unwrap();
|
||||
let secret_bytes = ed25519::ComponentBytes::try_from(&bytes[ed25519_dalek::SECRET_KEY_LENGTH..ed25519_dalek::SECRET_KEY_LENGTH + ed25519_dalek::PUBLIC_KEY_LENGTH]).unwrap();
|
||||
(ActorId(id_bytes), ActorSecret(secret_bytes))
|
||||
}
|
||||
|
||||
pub fn verify(&self, data: &[u8], signature: &ActorSignature) -> Result<(), ed25519::signature::Error> {
|
||||
let key = ed25519_dalek::VerifyingKey::from_bytes(&self.0).unwrap();
|
||||
key.verify(data, &signature.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl ActorSecret {
|
||||
pub fn sign(&self, data: &[u8]) -> ActorSignature {
|
||||
let key = ed25519_dalek::SigningKey::from_bytes(&self.0);
|
||||
ActorSignature(key.sign(data))
|
||||
}
|
||||
|
||||
pub fn get_id(&self) -> ActorId {
|
||||
let key = ed25519_dalek::SigningKey::from_bytes(&self.0);
|
||||
ActorId(key.verifying_key().to_bytes())
|
||||
}
|
||||
}
|
||||
|
||||
impl EventId {
|
||||
/// creates a Sha2 event id
|
||||
pub fn from_hash(data: &[u8]) -> Self {
|
||||
use sha2::Digest;
|
||||
let hash = sha2::Sha256::digest(data);
|
||||
Self(EventHash::Sha2(hash.to_vec()))
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for EventId {
|
||||
type Err = ();
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let s = s.strip_prefix("$").unwrap();
|
||||
let data = b64engine.decode(s).unwrap();
|
||||
let hash = match data.get(0) {
|
||||
Some(0x00) => EventHash::Debug(String::from_utf8(data[1..].to_vec()).unwrap()),
|
||||
Some(0x01) => {
|
||||
let vec = data[1..].to_vec();
|
||||
assert_ne!(vec.len(), 32);
|
||||
EventHash::Sha2(vec)
|
||||
},
|
||||
Some(_) => unimplemented!(),
|
||||
None => panic!("missing version byte"),
|
||||
};
|
||||
Ok(EventId(hash))
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for EventId {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let (prefix, bytes) = match &self.0 {
|
||||
EventHash::Debug(d) => (0x00, d.as_bytes()),
|
||||
EventHash::Sha2(d) => (0x01, d.as_ref()),
|
||||
};
|
||||
let data: Vec<u8> = [prefix].into_iter().chain(bytes.to_vec()).collect();
|
||||
write!(f, "${}", b64engine.encode(data))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<EventId> for String {
|
||||
fn from(value: EventId) -> Self {
|
||||
value.to_string()
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&str> for EventId {
|
||||
type Error = String;
|
||||
|
||||
fn try_from(value: &str) -> Result<Self, Self::Error> {
|
||||
Ok(EventId::from_str(value).unwrap())
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for EventId {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
Display::fmt(self, f)
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for ActorId {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
Display::fmt(self, f)
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for ActorId {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "^{}", b64engine.encode(&self.0))
|
||||
}
|
||||
}
|
||||
|
||||
impl Serialize for ActorId {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer {
|
||||
serializer.serialize_str(&format!("{}", self))
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for ActorSignature {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "ActorSignature {{ {} }}", b64engine.encode(&self.0.to_bytes()))
|
||||
}
|
||||
}
|
||||
|
||||
// impl Debug for ActorSecret {
|
||||
// fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
// write!(f, "ActorSecret {{ <redacted> }}")
|
||||
// }
|
||||
// }
|
||||
|
||||
macro_rules! impl_display {
|
||||
($Thing:ty, $fmt:expr) => {
|
||||
impl Display for $Thing {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, $fmt, self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for $Thing {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
Display::fmt(self, f)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl_display!(RoomId, "!{}");
|
||||
|
||||
impl Serialize for ActorSignature {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer {
|
||||
serializer.serialize_str(&b64engine.encode(&self.0.to_bytes()))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for ActorSignature {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de> {
|
||||
#[derive(Deserialize)]
|
||||
struct Wrapper(String);
|
||||
let wrapped = Wrapper::deserialize(deserializer)?;
|
||||
let bytes = b64engine.decode(wrapped.0).unwrap();
|
||||
let sig = ed25519_dalek::Signature::from_slice(&bytes).unwrap();
|
||||
Ok(ActorSignature(sig))
|
||||
}
|
||||
}
|
31
src/bin/main.rs
Normal file
31
src/bin/main.rs
Normal file
|
@ -0,0 +1,31 @@
|
|||
use dag_resolve::{
|
||||
actor::ActorId,
|
||||
event::{CoreContent, HashType, SignatureType},
|
||||
resolvers::kv::{KVEventContent, KVResolver},
|
||||
room::Room,
|
||||
};
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let (_actor, secret) = ActorId::new(SignatureType::Ed25519);
|
||||
let resolver = Box::new(KVResolver);
|
||||
let mut room = Room::builder()
|
||||
.with_resolver(resolver)
|
||||
.with_hasher(HashType::Sha256)
|
||||
.with_signer(SignatureType::Ed25519)
|
||||
.create(&secret)?;
|
||||
room.create_event(
|
||||
CoreContent::Custom(KVEventContent::Set("foo".into(), "apple".into())),
|
||||
&secret,
|
||||
)?;
|
||||
room.create_event(
|
||||
CoreContent::Custom(KVEventContent::Set("bar".into(), "banana".into())),
|
||||
&secret,
|
||||
)?;
|
||||
room.create_event(
|
||||
CoreContent::Custom(KVEventContent::Set("baz".into(), "carrot".into())),
|
||||
&secret,
|
||||
)?;
|
||||
dbg!(&room);
|
||||
dbg!(&room.get_state());
|
||||
Ok(())
|
||||
}
|
42
src/error.rs
42
src/error.rs
|
@ -1,6 +1,46 @@
|
|||
use std::string::FromUtf8Error;
|
||||
|
||||
use thiserror::Error;
|
||||
|
||||
use crate::event::{EventId, HashType, SignatureType};
|
||||
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum Error {}
|
||||
pub enum Error {
|
||||
#[error("{0}")]
|
||||
Serde(#[from] serde_json::Error),
|
||||
|
||||
#[error("{0}")]
|
||||
CanonicalJson(#[from] canonical_json::CanonicalJSONError),
|
||||
|
||||
#[error("{0}")]
|
||||
Base64(#[from] base64::DecodeError),
|
||||
|
||||
#[error("{0}")]
|
||||
Ed25519(#[from] ed25519::Error),
|
||||
|
||||
#[error("{0}")]
|
||||
InvalidUtf8(#[from] FromUtf8Error),
|
||||
|
||||
#[error("Invalid length: expected {expected} bytes, got {got} bytes")]
|
||||
InvalidLength { expected: usize, got: usize },
|
||||
|
||||
#[error("Mismatched signer: expected {expected:?}, got {got:?}")]
|
||||
MismatchedSigner {
|
||||
expected: SignatureType,
|
||||
got: SignatureType,
|
||||
},
|
||||
|
||||
#[error("Mismatched hasher: expected {expected:?}, got {got:?}")]
|
||||
MismatchedHasher { expected: HashType, got: HashType },
|
||||
|
||||
#[error("Mismatched hash: expected {expected:?}, got {got:?}")]
|
||||
MismatchedHash { expected: EventId, got: EventId },
|
||||
|
||||
#[error("Missing data in builder")]
|
||||
MissingBuilderData,
|
||||
|
||||
#[error("Invalid starting sigil (char)")]
|
||||
InvalidSigil,
|
||||
}
|
||||
|
|
160
src/event.rs
160
src/event.rs
|
@ -1,160 +0,0 @@
|
|||
use crate::atoms::{ActorId, ActorSecret, ActorSignature, EventId};
|
||||
use std::fmt::Debug;
|
||||
use serde::{Serialize, Deserialize};
|
||||
|
||||
#[derive(Debug)]
|
||||
/// An event, is the basic building block/envolope containing all data.
|
||||
pub struct Event<T> {
|
||||
pub id: EventId,
|
||||
pub content: CoreContent<T>,
|
||||
pub references: Vec<EventId>,
|
||||
pub author: ActorId,
|
||||
pub signature: ActorSignature,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct EventBuilder<T> {
|
||||
content: CoreContent<T>,
|
||||
references: Vec<EventId>,
|
||||
author: ActorId,
|
||||
#[serde(skip)]
|
||||
author_secret: ActorSecret,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct EventBuilderHasId<T> {
|
||||
id: EventId,
|
||||
content: CoreContent<T>,
|
||||
references: Vec<EventId>,
|
||||
author: ActorId,
|
||||
#[serde(skip)]
|
||||
author_secret: ActorSecret,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct EventVerify<T> {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
id: Option<EventId>,
|
||||
content: CoreContent<T>,
|
||||
references: Vec<EventId>,
|
||||
author: ActorId,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
signature: Option<ActorSignature>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
/// This defines content of an event as either a builtin event or application-specific event.
|
||||
pub enum CoreContent<T> {
|
||||
Create(CreateContent),
|
||||
Custom(T),
|
||||
/*
|
||||
custom events from other places, for inspiration
|
||||
|
||||
ufh (my failed project):
|
||||
- x.tag{.local} to add tags to events
|
||||
- x.annotate{.local} to add custom data to events
|
||||
- x.acl to define access control
|
||||
- x.update to update/edit events
|
||||
- x.redact to remove events
|
||||
- x.file to create new files
|
||||
|
||||
matrix:
|
||||
- m.room.create to create rooms
|
||||
- m.room.join_rules to define who can join/leave
|
||||
- m.room.member to define room members
|
||||
- m.room.power_levels to define basic access control
|
||||
- m.room.canonical_alias to add human-readable aliases to rooms
|
||||
- m.room.redaction to remove events
|
||||
- m.room.tombstone to replace rooms
|
||||
- m.space.* for many:many room hierarchies
|
||||
*/
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
/// Every room has exactly one immutable create event as the single root of the event dag.
|
||||
pub struct CreateContent {
|
||||
pub resolver: String,
|
||||
pub hasher: HashType,
|
||||
pub signer: SignatureType,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub enum HashType {
|
||||
Debug = 0,
|
||||
Sha256 = 1,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub enum SignatureType {
|
||||
Debug = 0,
|
||||
Ed25519 = 1,
|
||||
}
|
||||
|
||||
impl<T: Debug + Serialize + Clone> Event<T> {
|
||||
pub fn builder(content: CoreContent<T>, actor: &ActorSecret) -> EventBuilder<T> {
|
||||
EventBuilder::new(content, actor)
|
||||
}
|
||||
|
||||
pub fn verify(&self) -> Result<(), ()> {
|
||||
let mut shallow = EventVerify {
|
||||
id: None,
|
||||
content: self.content.clone(),
|
||||
references: self.references.clone(),
|
||||
author: self.author.clone(),
|
||||
signature: None,
|
||||
};
|
||||
let value = serde_json::to_value(&shallow).unwrap();
|
||||
let data = canonical_json::to_string(&value).unwrap();
|
||||
assert_eq!(EventId::from_hash(data.as_bytes()), self.id);
|
||||
shallow.id = Some(self.id.clone());
|
||||
let value = serde_json::to_value(&shallow).unwrap();
|
||||
let data = dbg!(canonical_json::to_string(&value).unwrap());
|
||||
self.author.verify(data.as_bytes(), &self.signature).unwrap();
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Serialize> EventBuilder<T> {
|
||||
pub fn new(content: CoreContent<T>, actor: &ActorSecret) -> Self {
|
||||
EventBuilder {
|
||||
content,
|
||||
references: vec![],
|
||||
author: actor.get_id(),
|
||||
author_secret: actor.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_references(self, references: impl IntoIterator<Item = EventId>) -> Self {
|
||||
let new_refs = self.references.into_iter().chain(references).collect();
|
||||
Self {
|
||||
references: new_refs,
|
||||
..self
|
||||
}
|
||||
}
|
||||
|
||||
pub fn then_hash(self) -> EventBuilderHasId<T> {
|
||||
let value = serde_json::to_value(&self).unwrap();
|
||||
let data = canonical_json::to_string(&value).unwrap();
|
||||
EventBuilderHasId {
|
||||
id: EventId::from_hash(data.as_bytes()),
|
||||
content: self.content,
|
||||
references: self.references,
|
||||
author: self.author,
|
||||
author_secret: self.author_secret,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Serialize> EventBuilderHasId<T> {
|
||||
pub fn and_sign(self) -> Event<T> {
|
||||
let value = serde_json::to_value(&self).unwrap();
|
||||
let data = canonical_json::to_string(&value).unwrap();
|
||||
Event {
|
||||
id: self.id,
|
||||
content: self.content,
|
||||
references: self.references,
|
||||
author: self.author,
|
||||
signature: self.author_secret.sign(data.as_bytes()),
|
||||
}
|
||||
}
|
||||
}
|
8
src/lib.rs
Normal file
8
src/lib.rs
Normal file
|
@ -0,0 +1,8 @@
|
|||
#![allow(dead_code)] // TODO: remove this later!
|
||||
|
||||
pub mod error;
|
||||
pub mod resolvers;
|
||||
pub mod proto;
|
||||
|
||||
pub use error::{Error, Result};
|
||||
pub use proto::{actor, event, room, resolver};
|
36
src/main.rs
36
src/main.rs
|
@ -1,36 +0,0 @@
|
|||
#![allow(dead_code)] // TODO: remove this later!
|
||||
|
||||
use crate::{
|
||||
atoms::ActorId, event::{CoreContent, HashType, SignatureType}, resolvers::{KVEventContent, KVResolver}, room::Room
|
||||
};
|
||||
|
||||
mod atoms;
|
||||
mod error;
|
||||
mod event;
|
||||
mod resolver;
|
||||
mod resolvers;
|
||||
mod room;
|
||||
|
||||
fn main() {
|
||||
let (_actor, secret) = ActorId::new_pair();
|
||||
let resolver = Box::new(KVResolver);
|
||||
let mut room = Room::builder()
|
||||
.with_resolver(resolver)
|
||||
.with_hasher(HashType::Sha256)
|
||||
.with_signer(SignatureType::Ed25519)
|
||||
.create(&secret);
|
||||
room.create_event(CoreContent::Custom(KVEventContent::Set(
|
||||
"foo".into(),
|
||||
"apple".into(),
|
||||
)), &secret);
|
||||
room.create_event(CoreContent::Custom(KVEventContent::Set(
|
||||
"bar".into(),
|
||||
"banana".into(),
|
||||
)), &secret);
|
||||
room.create_event(CoreContent::Custom(KVEventContent::Set(
|
||||
"baz".into(),
|
||||
"carrot".into(),
|
||||
)), &secret);
|
||||
dbg!(&room);
|
||||
dbg!(&room.get_state());
|
||||
}
|
261
src/proto/actor.rs
Normal file
261
src/proto/actor.rs
Normal file
|
@ -0,0 +1,261 @@
|
|||
use base64::{engine::general_purpose::URL_SAFE_NO_PAD as b64engine, Engine};
|
||||
use ed25519_dalek::{Signer, Verifier};
|
||||
use rand::{distributions::Alphanumeric, Rng};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fmt::{Debug, Display};
|
||||
use std::result::Result as StdResult;
|
||||
|
||||
use crate::{event::SignatureType, Result};
|
||||
|
||||
#[derive(Hash, Clone, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub struct ActorId(ActorIdData);
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct ActorSecret(ActorSecretData);
|
||||
|
||||
#[derive(Clone, PartialEq, Eq)]
|
||||
pub struct ActorSignature(ActorSignatureData);
|
||||
|
||||
// NOTE: maybe use a trait instead, ie. {Signer, Hasher}
|
||||
// then put each sign/hash type behind a feature flag?
|
||||
#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
|
||||
enum ActorIdData {
|
||||
Debug(String),
|
||||
Ed25519(ed25519::ComponentBytes),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
enum ActorSecretData {
|
||||
Debug(String),
|
||||
Ed25519(ed25519::ComponentBytes),
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Eq)]
|
||||
enum ActorSignatureData {
|
||||
Debug(String),
|
||||
Ed25519(ed25519::Signature),
|
||||
}
|
||||
|
||||
impl ActorId {
|
||||
pub fn new(signature_type: SignatureType) -> (ActorId, ActorSecret) {
|
||||
match signature_type {
|
||||
SignatureType::Debug => {
|
||||
let rng = rand::thread_rng()
|
||||
.sample_iter(Alphanumeric)
|
||||
.map(char::from)
|
||||
.take(12)
|
||||
.collect();
|
||||
let secret_data = ActorSecretData::Debug(format!("{}-secret", rng));
|
||||
let id_data = ActorIdData::Debug(rng);
|
||||
(ActorId(id_data), ActorSecret(secret_data))
|
||||
}
|
||||
SignatureType::Ed25519 => {
|
||||
use ed25519_dalek::SigningKey;
|
||||
use rand::rngs::OsRng;
|
||||
|
||||
let mut csprng = OsRng;
|
||||
let signing_key: SigningKey = SigningKey::generate(&mut csprng);
|
||||
signing_key.verifying_key();
|
||||
let bytes = signing_key.to_keypair_bytes();
|
||||
let id_bytes =
|
||||
ed25519::ComponentBytes::try_from(&bytes[0..ed25519_dalek::SECRET_KEY_LENGTH])
|
||||
.expect("should always generate correct length");
|
||||
let secret_bytes = ed25519::ComponentBytes::try_from(
|
||||
&bytes[ed25519_dalek::SECRET_KEY_LENGTH
|
||||
..ed25519_dalek::SECRET_KEY_LENGTH + ed25519_dalek::PUBLIC_KEY_LENGTH],
|
||||
)
|
||||
.expect("should always generate correct length");
|
||||
let id_data = ActorIdData::Ed25519(id_bytes);
|
||||
let secret_data = ActorSecretData::Ed25519(secret_bytes);
|
||||
(ActorId(id_data), ActorSecret(secret_data))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn verify(&self, data: &[u8], signature: &ActorSignature) -> Result<()> {
|
||||
match (&self.0, &signature.0) {
|
||||
(ActorIdData::Debug(key_bytes), ActorSignatureData::Debug(sig_bytes)) => {
|
||||
assert_eq!(
|
||||
key_bytes.strip_suffix("-secret"),
|
||||
sig_bytes.strip_suffix("-signature")
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
(ActorIdData::Ed25519(key_bytes), ActorSignatureData::Ed25519(sig_bytes)) => {
|
||||
let key = ed25519_dalek::VerifyingKey::from_bytes(key_bytes)?;
|
||||
key.verify(data, sig_bytes)?;
|
||||
Ok(())
|
||||
}
|
||||
(_, _) => panic!("mismatched signatures"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_type(&self) -> SignatureType {
|
||||
match &self.0 {
|
||||
ActorIdData::Debug(_) => SignatureType::Debug,
|
||||
ActorIdData::Ed25519(_) => SignatureType::Ed25519,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ActorSecret {
|
||||
pub fn sign(&self, data: &[u8]) -> ActorSignature {
|
||||
match &self.0 {
|
||||
ActorSecretData::Debug(key_bytes) => {
|
||||
let sig = format!(
|
||||
"{}-signature",
|
||||
key_bytes
|
||||
.strip_suffix("-secret")
|
||||
.expect("already validated")
|
||||
);
|
||||
ActorSignature(ActorSignatureData::Debug(sig))
|
||||
}
|
||||
ActorSecretData::Ed25519(key_bytes) => {
|
||||
let key = ed25519_dalek::SigningKey::from_bytes(key_bytes);
|
||||
ActorSignature(ActorSignatureData::Ed25519(key.sign(data)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_id(&self) -> ActorId {
|
||||
match &self.0 {
|
||||
ActorSecretData::Debug(key_bytes) => ActorId(ActorIdData::Debug(
|
||||
key_bytes
|
||||
.strip_suffix("-secret")
|
||||
.expect("already validated")
|
||||
.to_string(),
|
||||
)),
|
||||
ActorSecretData::Ed25519(key_bytes) => {
|
||||
let key = ed25519_dalek::SigningKey::from_bytes(key_bytes);
|
||||
ActorId(ActorIdData::Ed25519(key.verifying_key().to_bytes()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_type(&self) -> SignatureType {
|
||||
match &self.0 {
|
||||
ActorSecretData::Debug(_) => SignatureType::Debug,
|
||||
ActorSecretData::Ed25519(_) => SignatureType::Ed25519,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ActorSignature {
|
||||
pub fn get_type(&self) -> SignatureType {
|
||||
match &self.0 {
|
||||
ActorSignatureData::Debug(_) => SignatureType::Debug,
|
||||
ActorSignatureData::Ed25519(_) => SignatureType::Ed25519,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for ActorId {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
Display::fmt(self, f)
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for ActorId {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let (prefix, bytes) = match &self.0 {
|
||||
ActorIdData::Debug(d) => (0x00, d.as_bytes()),
|
||||
ActorIdData::Ed25519(d) => (0x01, d.as_ref()),
|
||||
};
|
||||
let data: Vec<u8> = [prefix].into_iter().chain(bytes.to_vec()).collect();
|
||||
write!(f, "${}", b64engine.encode(data))
|
||||
}
|
||||
}
|
||||
|
||||
impl Serialize for ActorId {
|
||||
fn serialize<S>(&self, serializer: S) -> StdResult<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
serializer.serialize_str(&format!("^{}", self))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for ActorId {
|
||||
fn deserialize<D>(deserializer: D) -> StdResult<Self, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
#[derive(Deserialize)]
|
||||
struct Wrapper(String);
|
||||
let wrapped = Wrapper::deserialize(deserializer)?;
|
||||
let bytes = b64engine
|
||||
.decode(wrapped.0)
|
||||
.map_err(serde::de::Error::custom)?;
|
||||
match bytes.first() {
|
||||
Some(0x00) => {
|
||||
let id =
|
||||
String::from_utf8(bytes[1..].to_vec()).map_err(serde::de::Error::custom)?; // TODO: Error::invalid_data
|
||||
Ok(ActorId(ActorIdData::Debug(id)))
|
||||
}
|
||||
Some(0x01) => Ok(ActorId(ActorIdData::Ed25519(
|
||||
bytes[1..].try_into().map_err(serde::de::Error::custom)?, // TODO: Error::invalid_length
|
||||
))),
|
||||
Some(_) => unimplemented!("unsupported signature type"),
|
||||
None => panic!("missing type byte"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// impl Debug for ActorSecret {
|
||||
// fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
// write!(f, "ActorSecret {{ <redacted> }}")
|
||||
// }
|
||||
// }
|
||||
|
||||
impl Debug for ActorSignature {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let (prefix, bytes) = match &self.0 {
|
||||
ActorSignatureData::Debug(d) => (0x00, d.as_bytes().to_vec()),
|
||||
ActorSignatureData::Ed25519(d) => (0x01, d.to_bytes().to_vec()),
|
||||
};
|
||||
let data: Vec<u8> = [prefix].into_iter().chain(bytes).collect();
|
||||
write!(f, "ActorSignature {{ {} }}", b64engine.encode(data))
|
||||
}
|
||||
}
|
||||
|
||||
impl Serialize for ActorSignature {
|
||||
fn serialize<S>(&self, serializer: S) -> StdResult<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
let (prefix, bytes) = match &self.0 {
|
||||
ActorSignatureData::Debug(d) => (0x00, d.as_bytes().to_vec()),
|
||||
ActorSignatureData::Ed25519(d) => (0x01, d.to_bytes().to_vec()),
|
||||
};
|
||||
let data: Vec<u8> = [prefix].into_iter().chain(bytes).collect();
|
||||
serializer.serialize_bytes(&data)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for ActorSignature {
|
||||
fn deserialize<D>(deserializer: D) -> StdResult<Self, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
#[derive(Deserialize)]
|
||||
struct Wrapper(String);
|
||||
let wrapped = Wrapper::deserialize(deserializer)?;
|
||||
let bytes = b64engine
|
||||
.decode(wrapped.0)
|
||||
.map_err(serde::de::Error::custom)?;
|
||||
match bytes.first() {
|
||||
Some(0x00) => {
|
||||
let sig =
|
||||
String::from_utf8(bytes[1..].to_vec()).map_err(serde::de::Error::custom)?;
|
||||
Ok(ActorSignature(ActorSignatureData::Debug(sig)))
|
||||
}
|
||||
Some(0x01) => {
|
||||
let sig = ed25519_dalek::Signature::from_slice(&bytes[1..])
|
||||
.map_err(serde::de::Error::custom)?;
|
||||
Ok(ActorSignature(ActorSignatureData::Ed25519(sig)))
|
||||
}
|
||||
Some(_) => unimplemented!("unsupported signature type"),
|
||||
None => panic!("missing type byte"),
|
||||
}
|
||||
}
|
||||
}
|
307
src/proto/event.rs
Normal file
307
src/proto/event.rs
Normal file
|
@ -0,0 +1,307 @@
|
|||
use crate::{
|
||||
actor::{ActorId, ActorSecret, ActorSignature},
|
||||
room::Room,
|
||||
Error, Result,
|
||||
};
|
||||
use base64::{engine::general_purpose::URL_SAFE_NO_PAD as b64engine, Engine};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{
|
||||
fmt::{Debug, Display},
|
||||
str::FromStr,
|
||||
};
|
||||
|
||||
#[derive(Hash, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
|
||||
#[serde(try_from = "&str", into = "String")]
|
||||
pub struct EventId(EventIdData);
|
||||
|
||||
#[derive(Debug, Hash, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
|
||||
enum EventIdData {
|
||||
Debug(String),
|
||||
Sha2(Vec<u8>),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
/// An event, is the basic building block/envolope containing all data.
|
||||
pub struct Event<T> {
|
||||
id: EventId,
|
||||
content: CoreContent<T>,
|
||||
references: Vec<EventId>,
|
||||
author: ActorId,
|
||||
signature: ActorSignature,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct EventBuilder<T> {
|
||||
content: CoreContent<T>,
|
||||
references: Vec<EventId>,
|
||||
author: ActorId,
|
||||
#[serde(skip)]
|
||||
author_secret: ActorSecret,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct EventBuilderHasId<T> {
|
||||
id: EventId,
|
||||
content: CoreContent<T>,
|
||||
references: Vec<EventId>,
|
||||
author: ActorId,
|
||||
#[serde(skip)]
|
||||
author_secret: ActorSecret,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct EventVerify<T> {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
id: Option<EventId>,
|
||||
content: CoreContent<T>,
|
||||
references: Vec<EventId>,
|
||||
author: ActorId,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
signature: Option<ActorSignature>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
/// This defines content of an event as either a builtin event or application-specific event.
|
||||
pub enum CoreContent<T> {
|
||||
Create(CreateContent),
|
||||
Custom(T),
|
||||
/*
|
||||
custom events from other places, for inspiration
|
||||
|
||||
ufh (my failed project):
|
||||
- x.tag{.local} to add tags to events
|
||||
- x.annotate{.local} to add custom data to events
|
||||
- x.acl to define access control
|
||||
- x.update to update/edit events
|
||||
- x.redact to remove events
|
||||
- x.file to create new files
|
||||
|
||||
matrix:
|
||||
- m.room.create to create rooms
|
||||
- m.room.join_rules to define who can join/leave
|
||||
- m.room.member to define room members
|
||||
- m.room.power_levels to define basic access control
|
||||
- m.room.canonical_alias to add human-readable aliases to rooms
|
||||
- m.room.redaction to remove events
|
||||
- m.room.tombstone to replace rooms
|
||||
- m.space.* for many:many room hierarchies
|
||||
*/
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
/// Every room has exactly one immutable create event as the single root of the event dag.
|
||||
pub struct CreateContent {
|
||||
pub resolver: String,
|
||||
pub hasher: HashType,
|
||||
pub signer: SignatureType,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub enum HashType {
|
||||
Debug = 0,
|
||||
Sha256 = 1,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub enum SignatureType {
|
||||
Debug = 0,
|
||||
Ed25519 = 1,
|
||||
}
|
||||
|
||||
impl<T: Debug + Serialize + Clone> Event<T> {
|
||||
pub fn builder(content: CoreContent<T>, actor: &ActorSecret) -> EventBuilder<T> {
|
||||
EventBuilder::new(content, actor)
|
||||
}
|
||||
|
||||
pub fn verify<S>(&self, room: &Room<T, S>) -> Result<()> {
|
||||
let mut shallow = EventVerify {
|
||||
id: None,
|
||||
content: self.content.clone(),
|
||||
references: self.references.clone(),
|
||||
author: self.author.clone(),
|
||||
signature: None,
|
||||
};
|
||||
let room_config = match &room.get_root().content {
|
||||
CoreContent::Create(c) => c,
|
||||
_ => unreachable!("the root should always be a create event"),
|
||||
};
|
||||
|
||||
// verify id hash
|
||||
let value = serde_json::to_value(&shallow)?;
|
||||
let data = canonical_json::to_string(&value)?;
|
||||
let calculated_hash = EventId::from_hash(data.as_bytes());
|
||||
if calculated_hash != self.id {
|
||||
return Err(Error::MismatchedHash {
|
||||
expected: self.id.clone(),
|
||||
got: calculated_hash,
|
||||
});
|
||||
}
|
||||
if room_config.hasher != self.id.get_type() {
|
||||
return Err(Error::MismatchedHasher {
|
||||
expected: room_config.hasher,
|
||||
got: self.id.get_type(),
|
||||
});
|
||||
}
|
||||
shallow.id = Some(self.id.clone());
|
||||
|
||||
// verify signature
|
||||
let value = serde_json::to_value(&shallow)?;
|
||||
let data = dbg!(canonical_json::to_string(&value)?);
|
||||
self.author.verify(data.as_bytes(), &self.signature)?;
|
||||
if room_config.signer != self.author.get_type()
|
||||
|| room_config.signer != self.signature.get_type()
|
||||
{
|
||||
return Err(Error::MismatchedSigner {
|
||||
expected: room_config.signer,
|
||||
got: self.author.get_type(),
|
||||
});
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn id(&self) -> &EventId {
|
||||
&self.id
|
||||
}
|
||||
|
||||
pub fn author(&self) -> &ActorId {
|
||||
&self.author
|
||||
}
|
||||
|
||||
pub fn content(&self) -> &CoreContent<T> {
|
||||
&self.content
|
||||
}
|
||||
|
||||
pub fn references(&self) -> &Vec<EventId> {
|
||||
&self.references
|
||||
}
|
||||
|
||||
pub fn signature(&self) -> &ActorSignature {
|
||||
&self.signature
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Serialize> EventBuilder<T> {
|
||||
pub fn new(content: CoreContent<T>, actor: &ActorSecret) -> Self {
|
||||
EventBuilder {
|
||||
content,
|
||||
references: vec![],
|
||||
author: actor.get_id(),
|
||||
author_secret: actor.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_references(self, references: impl IntoIterator<Item = EventId>) -> Self {
|
||||
let new_refs = self.references.into_iter().chain(references).collect();
|
||||
Self {
|
||||
references: new_refs,
|
||||
..self
|
||||
}
|
||||
}
|
||||
|
||||
pub fn then_hash(self) -> Result<EventBuilderHasId<T>> {
|
||||
let value = serde_json::to_value(&self)?;
|
||||
let data = canonical_json::to_string(&value)?;
|
||||
Ok(EventBuilderHasId {
|
||||
id: EventId::from_hash(data.as_bytes()),
|
||||
content: self.content,
|
||||
references: self.references,
|
||||
author: self.author,
|
||||
author_secret: self.author_secret,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Serialize> EventBuilderHasId<T> {
|
||||
pub fn and_sign(self) -> Result<Event<T>> {
|
||||
let value = serde_json::to_value(&self)?;
|
||||
let data = canonical_json::to_string(&value)?;
|
||||
Ok(Event {
|
||||
id: self.id,
|
||||
content: self.content,
|
||||
references: self.references,
|
||||
author: self.author,
|
||||
signature: self.author_secret.sign(data.as_bytes()),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl EventId {
|
||||
/// creates a Sha2 event id
|
||||
pub fn from_hash(data: &[u8]) -> Self {
|
||||
use sha2::Digest;
|
||||
let hash = sha2::Sha256::digest(data);
|
||||
Self(EventIdData::Sha2(hash.to_vec()))
|
||||
}
|
||||
|
||||
pub fn get_type(&self) -> HashType {
|
||||
match &self.0 {
|
||||
EventIdData::Debug(_) => HashType::Debug,
|
||||
EventIdData::Sha2(_) => HashType::Sha256,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for EventId {
|
||||
type Err = Error;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self> {
|
||||
let s = s.strip_prefix('$').ok_or(Error::InvalidSigil)?;
|
||||
let data = b64engine.decode(s)?;
|
||||
let hash = match data.first() {
|
||||
Some(0x00) => EventIdData::Debug(String::from_utf8(data[1..].to_vec())?),
|
||||
Some(0x01) => {
|
||||
let vec = data[1..].to_vec();
|
||||
if vec.len() != 32 {
|
||||
return Err(Error::InvalidLength {
|
||||
expected: 32,
|
||||
got: vec.len(),
|
||||
});
|
||||
}
|
||||
EventIdData::Sha2(vec)
|
||||
}
|
||||
Some(_) => unimplemented!(),
|
||||
None => panic!("missing version byte"),
|
||||
};
|
||||
Ok(EventId(hash))
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for EventId {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let (prefix, bytes) = match &self.0 {
|
||||
EventIdData::Debug(d) => (0x00, d.as_bytes()),
|
||||
EventIdData::Sha2(d) => (0x01, d.as_ref()),
|
||||
};
|
||||
let data: Vec<u8> = [prefix].into_iter().chain(bytes.to_vec()).collect();
|
||||
write!(f, "${}", b64engine.encode(data))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<EventId> for String {
|
||||
fn from(value: EventId) -> Self {
|
||||
value.to_string()
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&str> for EventId {
|
||||
type Error = Error;
|
||||
|
||||
fn try_from(value: &str) -> Result<Self> {
|
||||
Ok(EventId::from_str(value)?)
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for EventId {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
Display::fmt(self, f)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> PartialEq for Event<T> {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.id == other.id
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Eq for Event<T> {}
|
4
src/proto/mod.rs
Normal file
4
src/proto/mod.rs
Normal file
|
@ -0,0 +1,4 @@
|
|||
pub mod actor;
|
||||
pub mod event;
|
||||
pub mod room;
|
||||
pub mod resolver;
|
|
@ -1,26 +1,23 @@
|
|||
use crate::{atoms::EventId, event::Event};
|
||||
use serde::Serialize;
|
||||
|
||||
use crate::event::{EventId, Event};
|
||||
use std::{cmp::Ordering, collections::HashSet, fmt::Debug};
|
||||
|
||||
/// small shards of code designed to resolve state
|
||||
pub trait Resolver<Type, State> {
|
||||
|
||||
/// Given a set of ordered events, resolve the final state
|
||||
fn resolve(&self, events: &[&Event<Type>]) -> State;
|
||||
|
||||
/// Given two events, decide which one comes first
|
||||
/// if Ordering::Equal is returned, the event id is used
|
||||
fn tiebreak(
|
||||
&self,
|
||||
a: &Event<Type>,
|
||||
b: &Event<Type>,
|
||||
) -> Ordering;
|
||||
fn tiebreak(&self, a: &Event<Type>, b: &Event<Type>) -> Ordering;
|
||||
|
||||
/// TEMP: Get the name/id of this resolver
|
||||
fn name(&self) -> &str;
|
||||
}
|
||||
|
||||
/// topologically sort a list of events
|
||||
pub fn sort<T>(
|
||||
pub fn sort<T: Debug + Serialize + Clone>(
|
||||
tiebreak: impl Fn(&Event<T>, &Event<T>) -> Ordering,
|
||||
events: &[Event<T>],
|
||||
) -> Vec<&Event<T>> {
|
||||
|
@ -28,23 +25,23 @@ pub fn sort<T>(
|
|||
.iter()
|
||||
.flat_map(|event| {
|
||||
event
|
||||
.references
|
||||
.references()
|
||||
.iter()
|
||||
.map(|parent_id| (parent_id.clone(), event.id.clone()))
|
||||
.map(|parent_id| (parent_id.clone(), event.id().clone()))
|
||||
})
|
||||
.collect();
|
||||
let mut sorted = vec![];
|
||||
let (mut heads, mut unsorted): (Vec<_>, Vec<_>) =
|
||||
events.iter().partition(|event| event.references.is_empty());
|
||||
events.iter().partition(|event| event.references().is_empty());
|
||||
assert!(heads.len() == 1);
|
||||
while let Some(ev) = heads.pop() {
|
||||
references.retain(|(parent_id, _)| parent_id != &ev.id);
|
||||
references.retain(|(parent_id, _)| parent_id != ev.id());
|
||||
sorted.push(ev);
|
||||
let (new_unsorted, mut children) = unsorted
|
||||
.into_iter()
|
||||
.partition(|candidate| references.iter().any(|(_, child)| child == &candidate.id));
|
||||
.partition(|candidate| references.iter().any(|(_, child)| child == candidate.id()));
|
||||
unsorted = new_unsorted;
|
||||
children.sort_by(|a, b| tiebreak(a, b).then_with(|| a.id.cmp(&b.id)));
|
||||
children.sort_by(|a, b| tiebreak(a, b).then_with(|| a.id().cmp(&b.id())));
|
||||
heads.extend(children);
|
||||
}
|
||||
assert!(unsorted.is_empty());
|
|
@ -1,9 +1,7 @@
|
|||
use serde::Serialize;
|
||||
|
||||
use crate::{
|
||||
atoms::{ActorSecret, EventId},
|
||||
event::{CoreContent, CreateContent, Event, HashType, SignatureType},
|
||||
resolver::{sort, Resolver},
|
||||
actor::ActorSecret, event::EventId, event::{CoreContent, CreateContent, Event, HashType, SignatureType}, resolver::{sort, Resolver}, Error, Result
|
||||
};
|
||||
use std::fmt::Debug;
|
||||
|
||||
|
@ -25,7 +23,7 @@ impl<T: Debug + Serialize + Clone, S> Room<T, S> {
|
|||
hasher: HashType,
|
||||
signer: SignatureType,
|
||||
secret: &ActorSecret,
|
||||
) -> Self {
|
||||
) -> Result<Self> {
|
||||
let base_event = Event::builder(
|
||||
CoreContent::Create(CreateContent {
|
||||
resolver: resolver_name.into(),
|
||||
|
@ -34,13 +32,13 @@ impl<T: Debug + Serialize + Clone, S> Room<T, S> {
|
|||
}),
|
||||
secret,
|
||||
)
|
||||
.then_hash()
|
||||
.and_sign();
|
||||
Self {
|
||||
heads: vec![base_event.id.clone()],
|
||||
.then_hash()?
|
||||
.and_sign()?;
|
||||
Ok(Self {
|
||||
heads: vec![base_event.id().clone()],
|
||||
events: vec![base_event],
|
||||
resolver,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn get_root(&self) -> &Event<T> {
|
||||
|
@ -60,17 +58,18 @@ impl<T: Debug + Serialize + Clone, S> Room<T, S> {
|
|||
resolver.resolve(&sorted)
|
||||
}
|
||||
|
||||
pub fn create_event(&mut self, event_content: CoreContent<T>, secret: &ActorSecret) {
|
||||
pub fn create_event(&mut self, event_content: CoreContent<T>, secret: &ActorSecret) -> Result<()> {
|
||||
let event = Event::builder(event_content, secret)
|
||||
.with_references(std::mem::take(&mut self.heads))
|
||||
.then_hash()
|
||||
.and_sign();
|
||||
.then_hash()?
|
||||
.and_sign()?;
|
||||
self.append_event(event);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn append_event(&mut self, event: Event<T>) {
|
||||
event.verify().expect("event failed verification");
|
||||
let event_id = event.id.clone();
|
||||
event.verify(self).expect("event failed verification");
|
||||
let event_id = event.id().clone();
|
||||
self.events.push(event);
|
||||
self.heads = vec![event_id];
|
||||
}
|
||||
|
@ -106,12 +105,12 @@ impl<T: Clone + Debug + Serialize, S> RoomBuilder<T, S> {
|
|||
self
|
||||
}
|
||||
|
||||
pub fn create(self, secret: &ActorSecret) -> Room<T, S> {
|
||||
pub fn create(self, secret: &ActorSecret) -> Result<Room<T, S>> {
|
||||
Room::<T, S>::new(
|
||||
self.resolver.as_ref().unwrap().name().to_owned(),
|
||||
self.resolver.unwrap(),
|
||||
self.hasher.unwrap(),
|
||||
self.signer.unwrap(),
|
||||
self.resolver.as_ref().ok_or(Error::MissingBuilderData)?.name().to_owned(),
|
||||
self.resolver.ok_or(Error::MissingBuilderData)?,
|
||||
self.hasher.ok_or(Error::MissingBuilderData)?,
|
||||
self.signer.ok_or(Error::MissingBuilderData)?,
|
||||
secret,
|
||||
)
|
||||
}
|
|
@ -1,12 +1,13 @@
|
|||
//! contains premade resolvers, for testing
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{
|
||||
event::{CoreContent, Event}, resolver::Resolver
|
||||
event::{CoreContent, Event},
|
||||
resolver::Resolver,
|
||||
};
|
||||
use std::{cmp::Ordering, collections::BTreeMap};
|
||||
|
||||
///! contains premade resolvers, for testing
|
||||
|
||||
#[derive(Debug)]
|
||||
/// A basic key-value store
|
||||
pub struct KVResolver;
|
||||
|
@ -26,7 +27,7 @@ impl Resolver<KVEventContent, KVState> for KVResolver {
|
|||
dbg!(events);
|
||||
let mut kv = BTreeMap::new();
|
||||
for event in events {
|
||||
match &event.content {
|
||||
match &event.content() {
|
||||
CoreContent::Create(_) => {}
|
||||
CoreContent::Custom(KVEventContent::Set(k, v)) => {
|
||||
kv.insert(k.clone(), v.clone());
|
3
src/resolvers/mod.rs
Normal file
3
src/resolvers/mod.rs
Normal file
|
@ -0,0 +1,3 @@
|
|||
//! contains premade resolvers, for testing
|
||||
|
||||
pub mod kv;
|
6
tests/hello.rs
Normal file
6
tests/hello.rs
Normal file
|
@ -0,0 +1,6 @@
|
|||
// TODO: write tests
|
||||
|
||||
#[test]
|
||||
fn test_hello() {
|
||||
assert_eq!(2 + 2, 4);
|
||||
}
|
30
tests/kv.rs
Normal file
30
tests/kv.rs
Normal file
|
@ -0,0 +1,30 @@
|
|||
use dag_resolve::{
|
||||
actor::ActorId,
|
||||
event::{CoreContent, HashType, SignatureType},
|
||||
resolvers::kv::{KVEventContent, KVResolver},
|
||||
room::Room,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn test_example() {
|
||||
let (_actor, secret) = ActorId::new(SignatureType::Ed25519);
|
||||
let resolver = Box::new(KVResolver);
|
||||
let mut room = Room::builder()
|
||||
.with_resolver(resolver)
|
||||
.with_hasher(HashType::Sha256)
|
||||
.with_signer(SignatureType::Ed25519)
|
||||
.create(&secret)
|
||||
.unwrap();
|
||||
room.create_event(
|
||||
CoreContent::Custom(KVEventContent::Set("foo".into(), "apple".into())),
|
||||
&secret,
|
||||
).unwrap();
|
||||
room.create_event(
|
||||
CoreContent::Custom(KVEventContent::Set("bar".into(), "banana".into())),
|
||||
&secret,
|
||||
).unwrap();
|
||||
room.create_event(
|
||||
CoreContent::Custom(KVEventContent::Set("baz".into(), "carrot".into())),
|
||||
&secret,
|
||||
).unwrap();
|
||||
}
|
Loading…
Reference in a new issue