refactor and add/use error type

This commit is contained in:
tezlm 2024-02-09 15:17:20 -08:00
parent 81329e55ee
commit 575d9a353e
Signed by: tezlm
GPG key ID: 649733FCD94AFBBA
15 changed files with 725 additions and 455 deletions

View file

@ -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
View 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(())
}

View file

@ -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,
}

View file

@ -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
View 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};

View file

@ -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
View 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
View 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
View file

@ -0,0 +1,4 @@
pub mod actor;
pub mod event;
pub mod room;
pub mod resolver;

View file

@ -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());

View file

@ -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,
)
}

View file

@ -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
View file

@ -0,0 +1,3 @@
//! contains premade resolvers, for testing
pub mod kv;

6
tests/hello.rs Normal file
View file

@ -0,0 +1,6 @@
// TODO: write tests
#[test]
fn test_hello() {
assert_eq!(2 + 2, 4);
}

30
tests/kv.rs Normal file
View 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();
}