From 324b1c7d730be7b8526a6341bb162a06cb6a2d0c Mon Sep 17 00:00:00 2001 From: tezlm Date: Thu, 8 Feb 2024 11:36:08 -0800 Subject: [PATCH] hash + verify events --- Cargo.lock | 90 +++++++++++++++++++++++++++++++++++++++++++++++- Cargo.toml | 4 ++- src/atoms.rs | 85 ++++++++++++++++++++++++++++++++++++++------- src/event.rs | 55 ++++++++++++++++++++++++----- src/resolvers.rs | 4 ++- src/room.rs | 10 +++--- 6 files changed, 218 insertions(+), 30 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f89ef2c..75f73b6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,21 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "aho-corasick" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" +dependencies = [ + "memchr", +] + +[[package]] +name = "base64" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + [[package]] name = "base64ct" version = "1.6.0" @@ -17,6 +32,19 @@ dependencies = [ "generic-array", ] +[[package]] +name = "canonical_json" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f89083fd014d71c47a718d7f4ac050864dac8587668dbe90baf9e261064c5710" +dependencies = [ + "hex", + "regex", + "serde", + "serde_json", + "thiserror", +] + [[package]] name = "cfg-if" version = "1.0.0" @@ -80,11 +108,13 @@ dependencies = [ name = "dag-resolve" version = "0.1.0" dependencies = [ + "base64", + "canonical_json", "ed25519", "ed25519-dalek", - "hex", "rand", "serde", + "serde_json", "sha2", "thiserror", ] @@ -167,12 +197,24 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "itoa" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" + [[package]] name = "libc" version = "0.2.153" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" +[[package]] +name = "memchr" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" + [[package]] name = "pkcs8" version = "0.10.2" @@ -243,6 +285,35 @@ dependencies = [ "getrandom", ] +[[package]] +name = "regex" +version = "1.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b62dbe01f0b06f9d8dc7d49e05a0785f153b00b2c227856282f671e0318c9b15" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5bb987efffd3c6d0d8f5f89510bb458559eab11e4f869acb20bf845e016259cd" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" + [[package]] name = "rustc_version" version = "0.4.0" @@ -252,6 +323,12 @@ dependencies = [ "semver", ] +[[package]] +name = "ryu" +version = "1.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c" + [[package]] name = "semver" version = "1.0.21" @@ -278,6 +355,17 @@ dependencies = [ "syn", ] +[[package]] +name = "serde_json" +version = "1.0.113" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69801b70b1c3dac963ecb03a364ba0ceda9cf60c71cfe475e99864759c8b8a79" +dependencies = [ + "itoa", + "ryu", + "serde", +] + [[package]] name = "sha2" version = "0.10.8" diff --git a/Cargo.toml b/Cargo.toml index 76dba08..0963f6d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,10 +6,12 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +base64 = "0.21.7" +canonical_json = "0.5.0" ed25519 = "2.2.3" ed25519-dalek = { version = "2.1.1", features = ["rand_core"] } -hex = "0.4.3" rand = "0.8.5" serde = { version = "1.0.196", features = ["derive"] } +serde_json = "1.0.113" sha2 = "0.10.8" thiserror = "1.0.56" diff --git a/src/atoms.rs b/src/atoms.rs index fe60b31..ef77398 100644 --- a/src/atoms.rs +++ b/src/atoms.rs @@ -1,11 +1,13 @@ use std::fmt::{Debug, Display}; 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)] +#[derive(Hash, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] pub struct RoomId(String); -#[derive(Hash, Clone, PartialEq, Eq, PartialOrd, Ord)] +#[derive(Hash, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] pub struct EventId(String); #[derive(Hash, Clone, PartialEq, Eq, PartialOrd, Ord)] @@ -17,6 +19,34 @@ pub struct ActorSecret(ed25519::ComponentBytes); #[derive(Clone, PartialEq, Eq)] pub struct ActorSignature(ed25519::Signature); +/* TODO: implement other keypair methods, for extensibility + +enum ActorIdType { + Debug(String), + Ed25519(ed25519::ComponentBytes), + PostQuantum(Something), +} + +enum ActorSecretType { + Debug(String), + Ed25519(ed25519::ComponentBytes), + PostQuantum(Something), +} + +enum ActorSignatureType { + Debug(String), + Ed25519(ed25519::Signature), + PostQuantum(Something), +} + +// unsure how to format this +enum EventHash { + Debug(String), + Sha2_256(bytes), + Sha3_512(bytes), +} +*/ + 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 @@ -63,26 +93,36 @@ impl ActorSecret { } impl EventId { - // TEMP: will use hashes - pub fn random() -> Self { - let rng = rand::thread_rng() - .sample_iter(Alphanumeric) - .map(char::from) - .take(12) - .collect(); - Self(rng) + pub fn from_hash(data: &[u8]) -> Self { + use sha2::Digest; + let hash = sha2::Sha256::digest(data); + Self(format!("{}", b64engine.encode(&hash))) } } impl Debug for ActorId { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "^{}", hex::encode(&self.0)) + write!(f, "^{}", b64engine.encode(&self.0)) + } +} + +impl Display for ActorId { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + Debug::fmt(self, f) + } +} + +impl Serialize for ActorId { + fn serialize(&self, serializer: S) -> Result + 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 {{ {} }}", hex::encode(&self.0.to_bytes())) + write!(f, "ActorSignature {{ {} }}", b64engine.encode(&self.0.to_bytes())) } } @@ -110,3 +150,24 @@ macro_rules! impl_display { impl_display!(EventId, "${}"); impl_display!(RoomId, "!{}"); + +impl Serialize for ActorSignature { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer { + serializer.serialize_str(&b64engine.encode(&self.0.to_bytes())) + } +} + +impl<'de> Deserialize<'de> for ActorSignature { + fn deserialize(deserializer: D) -> Result + 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)) + } +} diff --git a/src/event.rs b/src/event.rs index d8ab332..a33fa3c 100644 --- a/src/event.rs +++ b/src/event.rs @@ -1,5 +1,7 @@ use crate::atoms::{ActorId, ActorSecret, ActorSignature, EventId}; use std::fmt::Debug; +use base64::{Engine as _, engine::general_purpose::URL_SAFE_NO_PAD as b64engine}; +use serde::{Serialize, Deserialize}; #[derive(Debug)] /// An event, is the basic building block/envolope containing all data. @@ -11,24 +13,37 @@ pub struct Event { pub signature: ActorSignature, } -#[derive(Debug)] +#[derive(Debug, Serialize)] pub struct EventBuilder { content: CoreContent, references: Vec, author: ActorId, + #[serde(skip)] author_secret: ActorSecret, } -#[derive(Debug)] +#[derive(Debug, Serialize)] pub struct EventBuilderHasId { id: EventId, content: CoreContent, references: Vec, author: ActorId, + #[serde(skip)] author_secret: ActorSecret, } -#[derive(Debug)] +#[derive(Debug, Serialize)] +pub struct EventVerify { + #[serde(skip_serializing_if = "Option::is_none")] + id: Option, + content: CoreContent, + references: Vec, + author: ActorId, + #[serde(skip_serializing_if = "Option::is_none")] + signature: Option, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] /// This defines content of an event as either a builtin event or application-specific event. pub enum CoreContent { Create(CreateContent), @@ -56,19 +71,37 @@ pub enum CoreContent { */ } -#[derive(Debug)] +#[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, } -impl Event { +impl Event { pub fn builder(content: CoreContent, actor: &ActorSecret) -> EventBuilder { 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 EventBuilder { +impl EventBuilder { pub fn new(content: CoreContent, actor: &ActorSecret) -> Self { EventBuilder { content, @@ -87,8 +120,10 @@ impl EventBuilder { } pub fn then_hash(self) -> EventBuilderHasId { + let value = serde_json::to_value(&self).unwrap(); + let data = canonical_json::to_string(&value).unwrap(); EventBuilderHasId { - id: EventId::random(), + id: EventId::from_hash(data.as_bytes()), content: self.content, references: self.references, author: self.author, @@ -97,14 +132,16 @@ impl EventBuilder { } } -impl EventBuilderHasId { +impl EventBuilderHasId { pub fn and_sign(self) -> Event { + 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(&[0x00, 0x01, 0x02, 0x03]), + signature: self.author_secret.sign(data.as_bytes()), } } } diff --git a/src/resolvers.rs b/src/resolvers.rs index e0de391..e1db5c0 100644 --- a/src/resolvers.rs +++ b/src/resolvers.rs @@ -1,3 +1,5 @@ +use serde::{Deserialize, Serialize}; + use crate::{ event::{CoreContent, Event}, resolver::Resolver }; @@ -26,7 +28,7 @@ impl PremadeResolver { /// A basic key-value store pub struct KVResolver; -#[derive(Debug)] +#[derive(Debug, Clone, Serialize, Deserialize)] pub enum KVEventContent { Set(String, String), } diff --git a/src/room.rs b/src/room.rs index ff9a9d8..7025e52 100644 --- a/src/room.rs +++ b/src/room.rs @@ -1,3 +1,5 @@ +use serde::Serialize; + use crate::{ atoms::{ActorSecret, EventId}, event::{CoreContent, CreateContent, Event}, @@ -12,7 +14,7 @@ pub struct Room { pub resolver: Box>, } -impl Room { +impl Room { pub fn new( resolver_name: impl Into, resolver: Box>, @@ -59,11 +61,7 @@ impl Room { } pub fn append_event(&mut self, event: Event) { - // let parents: Vec<_> = self - // .events - // .iter() - // .filter(|parent| event.references.contains(&parent.id)) - // .collect(); + event.verify().expect("event failed verification"); let event_id = event.id.clone(); self.events.push(event); self.heads = vec![event_id];