1use core::fmt;
12use std::sync::Arc;
13
14use chacha20::cipher::{KeyIvInit, StreamCipher};
15use chacha20::ChaCha20;
16use zeroize::Zeroize;
17
18use crate::errors::*;
19use crate::repo::{BranchInfo, Repo};
20use crate::store::Store;
21use crate::types::*;
22use crate::utils::*;
23
24impl fmt::Display for Event {
25 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
26 match self {
27 Self::V0(v0) => {
28 writeln!(f, "V0")?;
29 writeln!(f, "topic_sig: {}", v0.topic_sig)?;
30 writeln!(f, "peer_sig: {}", v0.peer_sig)?;
31 write!(f, "content: {}", v0.content)?;
32 Ok(())
33 }
34 }
35 }
36}
37
38impl fmt::Display for EventContentV0 {
39 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
40 writeln!(f, "V0")?;
41 writeln!(f, "topic: {}", self.topic)?;
42 writeln!(f, "publisher: {}", self.publisher)?;
43 writeln!(f, "seq: {}", self.seq)?;
44 writeln!(f, "blocks: {}", self.blocks.len())?;
45 let mut i = 0;
46 for block in &self.blocks {
47 writeln!(f, "========== {:03}: {}", i, block.id())?;
48 i += 1;
49 }
50 writeln!(f, "file ids: {}", self.file_ids.len())?;
51 let mut i = 0;
52 for file in &self.file_ids {
53 writeln!(f, "========== {:03}: {}", i, file)?;
54 i += 1;
55 }
56 writeln!(f, "key: {:?}", self.key)?;
57 Ok(())
58 }
59}
60
61impl Event {
62 pub fn new(
63 publisher: &PrivKey,
64 seq: u64,
65 commit: &Commit,
66 additional_blocks: &Vec<BlockId>,
67 repo: &Repo,
68 ) -> Result<Event, NgError> {
69 Ok(Event::V0(EventV0::new(
70 publisher,
71 seq,
72 commit,
73 additional_blocks,
74 repo,
75 )?))
76 }
77
78 pub fn seq_num(&self) -> u64 {
79 match self {
80 Event::V0(v0) => v0.content.seq,
81 }
82 }
83
84 pub fn topic_id(&self) -> &TopicId {
85 match self {
86 Event::V0(v0) => &v0.content.topic,
87 }
88 }
89
90 pub fn file_ids(&self) -> &Vec<ObjectId> {
91 match self {
92 Event::V0(v0) => &v0.content.file_ids,
93 }
94 }
95
96 pub fn publisher(&self) -> &PeerId {
97 match self {
98 Event::V0(v0) => &v0.content.publisher,
99 }
100 }
101
102 pub fn verify(&self) -> Result<(), NgError> {
103 match self {
104 Event::V0(v0) => v0.verify(),
105 }
106 }
107
108 pub fn open_with_info(&self, repo: &Repo, branch: &BranchInfo) -> Result<Commit, NgError> {
113 match self {
114 Self::V0(v0) => v0.open_with_info(repo, branch),
115 }
116 }
117
118 pub fn commit_id(&self) -> ObjectId {
119 match self {
120 Self::V0(v0) => v0.content.blocks[0].id(),
121 }
122 }
123
124 pub fn open(
125 &self,
126 store: &Store,
127 repo_id: &RepoId,
128 branch_id: &BranchId,
129 branch_secret: &ReadCapSecret,
130 ) -> Result<Commit, NgError> {
131 match self {
132 Self::V0(v0) => v0.open(store, repo_id, branch_id, branch_secret, true),
133 }
134 }
135
136 pub fn open_with_body(
137 &self,
138 store: &Store,
139 repo_id: &RepoId,
140 branch_id: &BranchId,
141 branch_secret: &ReadCapSecret,
142 with_body: bool,
143 ) -> Result<Commit, NgError> {
144 match self {
145 Self::V0(v0) => v0.open(store, repo_id, branch_id, branch_secret, with_body),
146 }
147 }
148
149 }
159
160impl EventV0 {
161 pub fn verify(&self) -> Result<(), NgError> {
162 let content_ser = serde_bare::to_vec(&self.content)?;
163 verify(&content_ser, self.topic_sig, self.content.topic)?;
164 match self.content.publisher {
165 PeerId::Forwarded(peer_id) => verify(&content_ser, self.peer_sig, peer_id)?,
166 PeerId::ForwardedObfuscated(_) => {
167 panic!("cannot verify an Event with obfuscated publisher")
168 }
169 PeerId::Direct(_) => panic!("direct events are not supported"),
170 }
171 if self.content.blocks.len() < 2 {
172 return Err(NgError::MalformedEvent);
174 }
175 Ok(())
176 }
177
178 pub fn derive_key(
179 repo_id: &RepoId,
180 branch_id: &BranchId,
181 branch_secret: &ReadCapSecret,
182 publisher: &PubKey,
183 ) -> [u8; blake3::OUT_LEN] {
184 let mut key_material = match (*repo_id, *branch_id, branch_secret.clone(), *publisher) {
185 (
186 PubKey::Ed25519PubKey(repo),
187 PubKey::Ed25519PubKey(branch),
188 SymKey::ChaCha20Key(branch_sec),
189 PubKey::Ed25519PubKey(publ),
190 ) => [repo, branch, branch_sec, publ].concat(),
191 (_, _, _, _) => panic!("cannot derive key with Montgomery key"),
192 };
193 let res = blake3::derive_key(
194 "NextGraph Event Commit ObjectKey ChaCha20 key",
195 key_material.as_slice(),
196 );
197 key_material.zeroize();
198 res
199 }
200
201 pub fn new(
202 publisher: &PrivKey,
203 seq: u64,
204 commit: &Commit,
205 additional_blocks: &Vec<BlockId>,
206 repo: &Repo,
207 ) -> Result<EventV0, NgError> {
208 let branch_id = commit.branch();
209 let repo_id = repo.id;
210 let store = Arc::clone(&repo.store);
211 let branch = repo.branch(branch_id)?;
212 let topic_id = &branch.topic.unwrap();
213 let topic_priv_key = branch
214 .topic_priv_key
215 .as_ref()
216 .ok_or(NgError::PermissionDenied)?;
217 let publisher_pubkey = publisher.to_pub();
218 let key = Self::derive_key(
219 &repo_id,
220 branch_id,
221 &branch.read_cap.as_ref().unwrap().key,
222 &publisher_pubkey,
223 );
224 let commit_key = commit.key().unwrap();
225 let mut encrypted_commit_key = Vec::from(commit_key.slice());
226 let mut nonce = seq.to_le_bytes().to_vec();
227 nonce.append(&mut vec![0; 4]);
228 let mut cipher = ChaCha20::new((&key).into(), (nonce.as_slice()).into());
229 cipher.apply_keystream(encrypted_commit_key.as_mut_slice());
230
231 let mut blocks = vec![];
232 for bid in commit.blocks().iter() {
233 blocks.push(store.get(bid)?);
234 }
235 for bid in additional_blocks.iter() {
236 blocks.push(store.get(bid)?);
237 }
238 let event_content = EventContentV0 {
239 topic: *topic_id,
240 publisher: PeerId::Forwarded(publisher_pubkey),
241 seq,
242 blocks,
243 file_ids: commit
244 .header()
245 .as_ref()
246 .map_or_else(|| vec![], |h| h.files().to_vec()),
247 key: encrypted_commit_key,
248 };
249 let event_content_ser = serde_bare::to_vec(&event_content).unwrap();
250 let topic_sig = sign(topic_priv_key, topic_id, &event_content_ser)?;
251 let peer_sig = sign(publisher, &publisher_pubkey, &event_content_ser)?;
252 Ok(EventV0 {
253 content: event_content,
254 topic_sig,
255 peer_sig,
256 })
257 }
258
259 pub fn open_with_info(&self, repo: &Repo, branch: &BranchInfo) -> Result<Commit, NgError> {
279 self.open(
280 &repo.store,
281 &repo.id,
282 &branch.id,
283 &branch.read_cap.as_ref().unwrap().key,
284 true,
285 )
286 }
287
288 pub fn open(
289 &self,
290 store: &Store,
291 repo_id: &RepoId,
292 branch_id: &BranchId,
293 branch_secret: &ReadCapSecret,
294 with_body: bool,
295 ) -> Result<Commit, NgError> {
296 self.verify()?;
298
299 let publisher_pubkey = self.content.publisher.get_pub_key();
300 let key = Self::derive_key(repo_id, branch_id, branch_secret, &publisher_pubkey);
301 let mut encrypted_commit_key = self.content.key.clone();
302 let mut nonce = self.content.seq.to_le_bytes().to_vec();
303 nonce.append(&mut vec![0; 4]);
304 let mut cipher = ChaCha20::new((&key).into(), (nonce.as_slice()).into());
305 cipher.apply_keystream(encrypted_commit_key.as_mut_slice());
306
307 let commit_key: SymKey = encrypted_commit_key.as_slice().try_into()?;
308
309 let mut first_id = None;
310 for block in &self.content.blocks {
311 let id = store.put(block)?;
312 if first_id.is_none() {
313 first_id = Some(id)
314 }
315 }
316 let commit_ref = ObjectRef::from_id_key(first_id.unwrap(), commit_key);
317 Ok(Commit::load(commit_ref, &store, with_body)?)
318 }
319}