hash + verify events

This commit is contained in:
tezlm 2024-02-08 11:36:08 -08:00
parent 0daa4be975
commit 324b1c7d73
Signed by: tezlm
GPG key ID: 649733FCD94AFBBA
6 changed files with 218 additions and 30 deletions

90
Cargo.lock generated
View file

@ -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"

View file

@ -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"

View file

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

View file

@ -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<T> {
pub signature: ActorSignature,
}
#[derive(Debug)]
#[derive(Debug, Serialize)]
pub struct EventBuilder<T> {
content: CoreContent<T>,
references: Vec<EventId>,
author: ActorId,
#[serde(skip)]
author_secret: ActorSecret,
}
#[derive(Debug)]
#[derive(Debug, Serialize)]
pub struct EventBuilderHasId<T> {
id: EventId,
content: CoreContent<T>,
references: Vec<EventId>,
author: ActorId,
#[serde(skip)]
author_secret: ActorSecret,
}
#[derive(Debug)]
#[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),
@ -56,19 +71,37 @@ pub enum CoreContent<T> {
*/
}
#[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<T> Event<T> {
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> EventBuilder<T> {
impl<T: Serialize> EventBuilder<T> {
pub fn new(content: CoreContent<T>, actor: &ActorSecret) -> Self {
EventBuilder {
content,
@ -87,8 +120,10 @@ impl<T> EventBuilder<T> {
}
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::random(),
id: EventId::from_hash(data.as_bytes()),
content: self.content,
references: self.references,
author: self.author,
@ -97,14 +132,16 @@ impl<T> EventBuilder<T> {
}
}
impl<T> EventBuilderHasId<T> {
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(&[0x00, 0x01, 0x02, 0x03]),
signature: self.author_secret.sign(data.as_bytes()),
}
}
}

View file

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

View file

@ -1,3 +1,5 @@
use serde::Serialize;
use crate::{
atoms::{ActorSecret, EventId},
event::{CoreContent, CreateContent, Event},
@ -12,7 +14,7 @@ pub struct Room<E, S> {
pub resolver: Box<dyn Resolver<Type = E, State = S>>,
}
impl<T: Debug, S> Room<T, S> {
impl<T: Debug + Serialize + Clone, S> Room<T, S> {
pub fn new(
resolver_name: impl Into<String>,
resolver: Box<dyn Resolver<Type = T, State = S>>,
@ -59,11 +61,7 @@ impl<T: Debug, S> Room<T, S> {
}
pub fn append_event(&mut self, event: Event<T>) {
// 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];