From fa07875cca36c09012aa552fe036fac1511f0a39 Mon Sep 17 00:00:00 2001 From: tezlm Date: Fri, 9 Feb 2024 16:40:03 -0800 Subject: [PATCH] basic cli for testing things --- Cargo.lock | 383 ++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 4 + src/bin/dag.rs | 144 ++++++++++++++++ src/bin/main.rs | 31 ---- src/proto/actor.rs | 118 ++++++++++--- src/proto/event.rs | 39 +++-- src/proto/resolver.rs | 2 +- src/proto/room.rs | 38 ++++- 8 files changed, 681 insertions(+), 78 deletions(-) create mode 100644 src/bin/dag.rs delete mode 100644 src/bin/main.rs diff --git a/Cargo.lock b/Cargo.lock index 75f73b6..3865ade 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,33 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "addr2line" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "ahash" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77c3a9648d43b9cd48db467b3f87fdd6e146bcc88ab0180006cef2179fe11d01" +dependencies = [ + "cfg-if", + "once_cell", + "version_check", + "zerocopy", +] + [[package]] name = "aho-corasick" version = "1.1.2" @@ -11,6 +38,84 @@ dependencies = [ "memchr", ] +[[package]] +name = "allocator-api2" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5" + +[[package]] +name = "anstream" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e2e1ebcb11de5c03c67de28a7df593d32191b44939c482e97702baaaa6ab6a5" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8901269c6307e8d93993578286ac0edf7f195079ffff5ebdeea6a59ffb7e36bc" + +[[package]] +name = "anstyle-parse" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7" +dependencies = [ + "anstyle", + "windows-sys", +] + +[[package]] +name = "anyhow" +version = "1.0.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "080e9890a082662b09c1ad45f567faeeb47f22b5fb23895fbe1e651e718e25ca" +dependencies = [ + "backtrace", +] + +[[package]] +name = "backtrace" +version = "0.3.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" +dependencies = [ + "addr2line", + "cc", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", +] + [[package]] name = "base64" version = "0.21.7" @@ -23,6 +128,12 @@ version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" +[[package]] +name = "bitflags" +version = "2.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf" + [[package]] name = "block-buffer" version = "0.10.4" @@ -45,12 +156,67 @@ dependencies = [ "thiserror", ] +[[package]] +name = "cc" +version = "1.0.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" +dependencies = [ + "libc", +] + [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "clap" +version = "4.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80c21025abd42669a92efc996ef13cfb2c5c627858421ea58d5c3b331a6c134f" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "458bf1f341769dfcf849846f65dffdf9146daa56bcd2a47cb4e1de9915567c99" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "307bc0538d5f0f83b8248db3087aa92fe504e4691294d0c96c0eabc33f47ba47" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce" + +[[package]] +name = "colorchoice" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" + [[package]] name = "const-oid" version = "0.9.6" @@ -108,11 +274,14 @@ dependencies = [ name = "dag-resolve" version = "0.1.0" dependencies = [ + "anyhow", "base64", "canonical_json", + "clap", "ed25519", "ed25519-dalek", "rand", + "rusqlite", "serde", "serde_json", "sha2", @@ -164,6 +333,18 @@ dependencies = [ "zeroize", ] +[[package]] +name = "fallible-iterator" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649" + +[[package]] +name = "fallible-streaming-iterator" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" + [[package]] name = "fiat-crypto" version = "0.2.6" @@ -191,6 +372,37 @@ dependencies = [ "wasi", ] +[[package]] +name = "gimli" +version = "0.28.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" + +[[package]] +name = "hashbrown" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" +dependencies = [ + "ahash", + "allocator-api2", +] + +[[package]] +name = "hashlink" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8094feaf31ff591f651a2664fb9cfd92bba7a60ce3197265e9482ebe753c8f7" +dependencies = [ + "hashbrown", +] + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + [[package]] name = "hex" version = "0.4.3" @@ -209,12 +421,47 @@ version = "0.2.153" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" +[[package]] +name = "libsqlite3-sys" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf4e226dcd58b4be396f7bd3c20da8fdee2911400705297ba7d2d7cc2c30f716" +dependencies = [ + "cc", + "pkg-config", + "vcpkg", +] + [[package]] name = "memchr" version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" +[[package]] +name = "miniz_oxide" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d811f3e15f28568be3407c8e7fdb6514c1cda3cb30683f15b6a1a1dc4ea14a7" +dependencies = [ + "adler", +] + +[[package]] +name = "object" +version = "0.32.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" + [[package]] name = "pkcs8" version = "0.10.2" @@ -225,6 +472,12 @@ dependencies = [ "spki", ] +[[package]] +name = "pkg-config" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2900ede94e305130c13ddd391e0ab7cbaeb783945ae07a279c268cb05109c6cb" + [[package]] name = "platforms" version = "3.3.0" @@ -314,6 +567,26 @@ version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" +[[package]] +name = "rusqlite" +version = "0.30.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a78046161564f5e7cd9008aff3b2990b3850dc8e0349119b98e8f251e099f24d" +dependencies = [ + "bitflags", + "fallible-iterator", + "fallible-streaming-iterator", + "hashlink", + "libsqlite3-sys", + "smallvec", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" + [[package]] name = "rustc_version" version = "0.4.0" @@ -386,6 +659,12 @@ dependencies = [ "rand_core", ] +[[package]] +name = "smallvec" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7" + [[package]] name = "spki" version = "0.7.3" @@ -396,6 +675,12 @@ dependencies = [ "der", ] +[[package]] +name = "strsim" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ee073c9e4cd00e28217186dbe12796d692868f432bf2e97ee73bed0c56dfa01" + [[package]] name = "subtle" version = "2.5.0" @@ -445,6 +730,18 @@ version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +[[package]] +name = "utf8parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + [[package]] name = "version_check" version = "0.9.4" @@ -457,6 +754,92 @@ version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" + +[[package]] +name = "zerocopy" +version = "0.7.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74d4d3961e53fa4c9a25a8637fc2bfaf2595b3d3ae34875568a5cf64787716be" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "zeroize" version = "1.7.0" diff --git a/Cargo.toml b/Cargo.toml index 0963f6d..a10b7fa 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,16 +1,20 @@ [package] name = "dag-resolve" +description = "experiment in directed acyclic graphs and reducers" version = "0.1.0" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +anyhow = { version = "1.0.79", features = ["backtrace"] } base64 = "0.21.7" canonical_json = "0.5.0" +clap = { version = "4.5.0", features = ["derive"] } ed25519 = "2.2.3" ed25519-dalek = { version = "2.1.1", features = ["rand_core"] } rand = "0.8.5" +rusqlite = { version = "0.30.0", features = ["bundled"] } serde = { version = "1.0.196", features = ["derive"] } serde_json = "1.0.113" sha2 = "0.10.8" diff --git a/src/bin/dag.rs b/src/bin/dag.rs new file mode 100644 index 0000000..a3e5387 --- /dev/null +++ b/src/bin/dag.rs @@ -0,0 +1,144 @@ +use std::{ + path::{Path, PathBuf}, + str::FromStr, +}; + +use clap::Parser; +use dag_resolve::{ + actor::{ActorId, ActorSecret}, + event::{CoreContent, HashType, SignatureType}, + resolvers::kv::{KVEventContent, KVResolver, KVState}, + room::Room, +}; +use rusqlite::{Connection, OpenFlags}; + +#[derive(Parser)] +#[clap(version, about)] +enum Cli { + /// Initialize a new repository + Init { repo: PathBuf }, + + /// Set a key in a repository + Set { + repo: PathBuf, + key: String, + value: String, + }, +} + +type Result = std::result::Result; + +struct State { + db: Connection, + room: Room, + secret: ActorSecret, +} + +impl State { + fn create_event(&mut self, content: CoreContent) -> Result<()> { + let event = self.room.create_event(content, &self.secret)?; + self.db.execute( + "INSERT INTO events (id, json) VALUES (?, ?)", + (event.id().to_string(), serde_json::to_string(&event)?), + )?; + Ok(()) + } +} + +fn get_repo(path: &Path, create: bool) -> Result { + match (create, path.try_exists()?) { + (true, true) => panic!("repo already exists"), + (false, false) => panic!("repo does not exist"), + (true, false) => { + // create new repo + let db = rusqlite::Connection::open(path)?; + db.execute_batch( + r#" + CREATE TABLE IF NOT EXISTS config ( + key TEXT PRIMARY KEY, + value ANY + ); + + CREATE TABLE IF NOT EXISTS events ( + id TEXT PRIMARY KEY, + json TEXT + ); + "#, + )?; + let (actor, secret) = ActorId::new(SignatureType::Ed25519); + let resolver = Box::new(KVResolver); + let room = Room::builder() + .with_resolver(resolver) + .with_hasher(HashType::Sha256) + .with_signer(SignatureType::Ed25519) + .create(&secret)?; + db.execute( + "INSERT INTO config VALUES (?, ?)", + ("actor_id", actor.to_string()), + )?; + db.execute( + "INSERT INTO config VALUES (?, ?)", + ("actor_secret", serde_json::to_string(&secret)?), + )?; + for event in &room.events { + db.execute( + "INSERT INTO events (id, json) VALUES (?, ?)", + (event.id().to_string(), serde_json::to_string(&event)?), + )?; + } + Ok(State { db, room, secret }) + } + (false, true) => { + // restore repo + let db = rusqlite::Connection::open(path)?; + let actor: ActorId = + db.query_row("SELECT value FROM config WHERE key='actor_id'", [], |row| { + row.get(0).map(|s: String| s.parse()) + })??; + let secret: ActorSecret = db.query_row( + "SELECT value FROM config WHERE key='actor_secret'", + [], + |row| row.get(0).map(|s: String| serde_json::from_str(&s)), + )??; + dbg!(&actor, &secret); + let mut q = db.prepare("SELECT json FROM events")?; + let mut rows = q.query([])?; + let mut room: Option> = None; + while let Some(row) = rows.next()? { + let s: String = dbg!(row.get(0)?); + let ev = dbg!(serde_json::from_str(&s)?); + match &mut room { + Some(r) => { + r.append_event(ev)?; + }, + None => { + let resolver = Box::new(KVResolver); + let r = Room::from_root(resolver, ev)?; + room = Some(r); + }, + } + } + drop(rows); + drop(q); + let room = room.unwrap(); + dbg!(&room); + Ok(State { db, room, secret }) + } + } +} + +fn main() -> Result<()> { + let cli = Cli::parse(); + match cli { + Cli::Init { repo } => { + get_repo(&repo, true)?; + }, + Cli::Set { repo, key, value } => { + let mut state = get_repo(&repo, false)?; + state.create_event(CoreContent::Custom(KVEventContent::Set(key, value)))?; + dbg!(&state.room); + dbg!(&state.room.get_state()); + } + }; + Ok(()) +} diff --git a/src/bin/main.rs b/src/bin/main.rs deleted file mode 100644 index d07d10a..0000000 --- a/src/bin/main.rs +++ /dev/null @@ -1,31 +0,0 @@ -use dag_resolve::{ - actor::ActorId, - event::{CoreContent, HashType, SignatureType}, - resolvers::kv::{KVEventContent, KVResolver}, - room::Room, -}; - -fn main() -> Result<(), Box> { - 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(()) -} diff --git a/src/proto/actor.rs b/src/proto/actor.rs index 6be9b90..57c0065 100644 --- a/src/proto/actor.rs +++ b/src/proto/actor.rs @@ -4,13 +4,15 @@ use rand::{distributions::Alphanumeric, Rng}; use serde::{Deserialize, Serialize}; use std::fmt::{Debug, Display}; use std::result::Result as StdResult; +use std::str::FromStr; +use crate::Error; use crate::{event::SignatureType, Result}; #[derive(Hash, Clone, PartialEq, Eq, PartialOrd, Ord)] pub struct ActorId(ActorIdData); -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Clone, PartialEq, Eq)] pub struct ActorSecret(ActorSecretData); #[derive(Clone, PartialEq, Eq)] @@ -162,7 +164,30 @@ impl Display for ActorId { ActorIdData::Ed25519(d) => (0x01, d.as_ref()), }; let data: Vec = [prefix].into_iter().chain(bytes.to_vec()).collect(); - write!(f, "${}", b64engine.encode(data)) + write!(f, "^{}", b64engine.encode(data)) + } +} + +impl FromStr for ActorId { + type Err = Error; + + fn from_str(s: &str) -> Result { + let s = s.strip_prefix('^').ok_or(Error::InvalidSigil)?; + let bytes = b64engine.decode(s)?; + match bytes.first() { + Some(0x00) => { + let id = String::from_utf8(bytes[1..].to_vec())?; + Ok(ActorId(ActorIdData::Debug(id))) + } + Some(0x01) => Ok(ActorId(ActorIdData::Ed25519( + bytes[1..].try_into().map_err(|_| Error::InvalidLength { + expected: 32, + got: bytes.len() - 1, + })?, + ))), + Some(_) => unimplemented!("unsupported signature type"), + None => panic!("missing type byte"), + } } } @@ -171,7 +196,7 @@ impl Serialize for ActorId { where S: serde::Serializer, { - serializer.serialize_str(&format!("^{}", self)) + serializer.serialize_str(&self.to_string()) } } @@ -183,30 +208,65 @@ impl<'de> Deserialize<'de> for ActorId { #[derive(Deserialize)] struct Wrapper(String); let wrapped = Wrapper::deserialize(deserializer)?; - let bytes = b64engine - .decode(wrapped.0) - .map_err(serde::de::Error::custom)?; + wrapped.0.parse().map_err(serde::de::Error::custom) + } +} + +impl Debug for ActorSecret { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "ActorSecret {{ }}") + } +} + +impl Serialize for ActorSecret { + fn serialize(&self, serializer: S) -> StdResult + where + S: serde::Serializer, + { + let (prefix, bytes) = match &self.0 { + ActorSecretData::Debug(d) => (0x00, d.as_bytes().to_vec()), + ActorSecretData::Ed25519(d) => (0x01, d.to_vec()), + }; + let data: Vec = [prefix].into_iter().chain(bytes).collect(); + if serializer.is_human_readable() { + serializer.serialize_str(&b64engine.encode(data)) + } else { + serializer.serialize_bytes(&data) + } + } +} + +impl<'de> Deserialize<'de> for ActorSecret { + fn deserialize(deserializer: D) -> StdResult + where + D: serde::Deserializer<'de>, + { + #[derive(Deserialize)] + #[serde(untagged)] + enum Wrapper { + Blobby(Vec), + Stringy(String), + } + let bytes = match Wrapper::deserialize(deserializer)? { + Wrapper::Blobby(b) => b, + Wrapper::Stringy(s) => b64engine.decode(s).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))) + let sig = + String::from_utf8(bytes[1..].to_vec()).map_err(serde::de::Error::custom)?; + Ok(ActorSecret(ActorSecretData::Debug(sig))) + } + Some(0x01) => { + let key = bytes[1..].try_into().map_err(serde::de::Error::custom)?; + Ok(ActorSecret(ActorSecretData::Ed25519(key))) } - 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 {{ }}") -// } -// } - impl Debug for ActorSignature { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let (prefix, bytes) = match &self.0 { @@ -228,7 +288,11 @@ impl Serialize for ActorSignature { ActorSignatureData::Ed25519(d) => (0x01, d.to_bytes().to_vec()), }; let data: Vec = [prefix].into_iter().chain(bytes).collect(); - serializer.serialize_bytes(&data) + if serializer.is_human_readable() { + serializer.serialize_str(&b64engine.encode(data)) + } else { + serializer.serialize_bytes(&data) + } } } @@ -237,12 +301,16 @@ impl<'de> Deserialize<'de> for ActorSignature { 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)?; + #[derive(Debug, Deserialize)] + #[serde(untagged)] + enum Wrapper { + Blobby(Vec), + Stringy(String), + } + let bytes = match Wrapper::deserialize(deserializer)? { + Wrapper::Blobby(b) => b, + Wrapper::Stringy(s) => b64engine.decode(s).map_err(serde::de::Error::custom)?, + }; match bytes.first() { Some(0x00) => { let sig = diff --git a/src/proto/event.rs b/src/proto/event.rs index c0270cd..eb6bfde 100644 --- a/src/proto/event.rs +++ b/src/proto/event.rs @@ -113,7 +113,7 @@ impl Event { EventBuilder::new(content, actor) } - pub fn verify(&self, room: &Room) -> Result<()> { + pub fn verify(&self) -> Result<()> { let mut shallow = EventVerify { id: None, content: self.content.clone(), @@ -121,10 +121,6 @@ impl Event { 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)?; @@ -136,18 +132,37 @@ impl Event { 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 self.author.get_type() != self.signature.get_type() { + return Err(Error::MismatchedSigner { + expected: self.author.get_type(), + got: self.signature.get_type(), + }); + } + + Ok(()) + } + + pub fn verify_room(&self, room: &Room) -> Result<()> { + self.verify()?; + + let room_config = match &room.get_root().content { + CoreContent::Create(c) => c, + _ => unreachable!("the root should always be a create event"), + }; + + if room_config.hasher != self.id.get_type() { + return Err(Error::MismatchedHasher { + expected: room_config.hasher, + got: self.id.get_type(), + }); + } + if room_config.signer != self.author.get_type() || room_config.signer != self.signature.get_type() { @@ -288,7 +303,7 @@ impl TryFrom<&str> for EventId { type Error = Error; fn try_from(value: &str) -> Result { - Ok(EventId::from_str(value)?) + EventId::from_str(value) } } diff --git a/src/proto/resolver.rs b/src/proto/resolver.rs index 2bb047c..d2ed909 100644 --- a/src/proto/resolver.rs +++ b/src/proto/resolver.rs @@ -41,7 +41,7 @@ pub fn sort( .into_iter() .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()); diff --git a/src/proto/room.rs b/src/proto/room.rs index 28d467e..bf03c4c 100644 --- a/src/proto/room.rs +++ b/src/proto/room.rs @@ -41,6 +41,17 @@ impl Room { }) } + pub fn from_root( + resolver: Box>, + event: Event, + ) -> Result { + Ok(Self { + heads: vec![event.id().clone()], + events: vec![event], + resolver, + }) + } + pub fn get_root(&self) -> &Event { self.events .first() @@ -58,20 +69,23 @@ impl Room { resolver.resolve(&sorted) } - pub fn create_event(&mut self, event_content: CoreContent, secret: &ActorSecret) -> Result<()> { + pub fn create_event(&mut self, event_content: CoreContent, secret: &ActorSecret) -> Result<&Event> { let event = Event::builder(event_content, secret) .with_references(std::mem::take(&mut self.heads)) .then_hash()? .and_sign()?; - self.append_event(event); - Ok(()) + self.append_event(event) } - pub fn append_event(&mut self, event: Event) { - event.verify(self).expect("event failed verification"); - let event_id = event.id().clone(); + pub fn append_event(&mut self, event: Event) -> Result<&Event> { + event.verify_room(self).expect("event failed verification"); + if self.events.iter().any(|p| p == &event) { + panic!("already exists"); + } self.events.push(event); - self.heads = vec![event_id]; + let event_ref = self.events.last().unwrap(); + self.heads = vec![event_ref.id().clone()]; + Ok(event_ref) } } @@ -81,14 +95,20 @@ pub struct RoomBuilder { signer: Option, } -impl RoomBuilder { - pub fn new() -> Self { +impl Default for RoomBuilder { + fn default() -> Self { Self { resolver: None, hasher: None, signer: None, } } +} + +impl RoomBuilder { + pub fn new() -> Self { + Self::default() + } pub fn with_resolver(mut self, resolver: Box>) -> Self { self.resolver = Some(resolver);