chaincraft_rust/examples/
chatroom.rs

1//! Chatroom Protocol - A dapp example for Chaincraft
2//!
3//! This demonstrates how to build a decentralized application on top of Chaincraft.
4//! The chatroom protocol allows:
5//! - Creating chatrooms with admin control
6//! - Requesting to join chatrooms
7//! - Admin approval of new members
8//! - Posting messages to chatrooms
9//! - Message validation and signature verification
10
11use crate::{
12    crypto::{
13        ecdsa::{ECDSASignature, ECDSAVerifier},
14        KeyType, PrivateKey, PublicKey, Signature,
15    },
16    error::{ChaincraftError, Result},
17    shared::{MessageType, SharedMessage, SharedObjectId},
18    shared_object::ApplicationObject,
19};
20use async_trait::async_trait;
21use serde::{Deserialize, Serialize};
22use serde_json::Value;
23use sha2::{Digest, Sha256};
24use std::any::Any;
25use std::collections::HashMap;
26use std::time::{SystemTime, UNIX_EPOCH};
27
28/// Chatroom message types
29#[derive(Debug, Clone, Serialize, Deserialize)]
30#[serde(tag = "message_type")]
31pub enum ChatroomMessageType {
32    #[serde(rename = "CREATE_CHATROOM")]
33    CreateChatroom {
34        chatroom_name: String,
35        public_key_pem: String,
36        #[serde(default)]
37        timestamp: f64,
38        #[serde(default)]
39        signature: String,
40    },
41    #[serde(rename = "REQUEST_JOIN")]
42    RequestJoin {
43        chatroom_name: String,
44        public_key_pem: String,
45        #[serde(default)]
46        timestamp: f64,
47        #[serde(default)]
48        signature: String,
49    },
50    #[serde(rename = "ACCEPT_MEMBER")]
51    AcceptMember {
52        chatroom_name: String,
53        public_key_pem: String,
54        requester_key_pem: String,
55        #[serde(default)]
56        timestamp: f64,
57        #[serde(default)]
58        signature: String,
59    },
60    #[serde(rename = "POST_MESSAGE")]
61    PostMessage {
62        chatroom_name: String,
63        public_key_pem: String,
64        text: String,
65        #[serde(default)]
66        timestamp: f64,
67        #[serde(default)]
68        signature: String,
69    },
70}
71
72/// A chatroom structure
73#[derive(Debug, Clone, Serialize, Deserialize)]
74pub struct Chatroom {
75    pub name: String,
76    pub admin: String,              // Admin's public key
77    pub members: Vec<String>,       // Member public keys
78    pub messages: Vec<ChatMessage>, // All messages including metadata
79}
80
81/// A chat message with metadata
82#[derive(Debug, Clone, Serialize, Deserialize)]
83pub struct ChatMessage {
84    pub message_type: String,
85    pub chatroom_name: String,
86    pub public_key_pem: String,
87    pub text: Option<String>,
88    pub requester_key_pem: Option<String>,
89    pub timestamp: f64,
90    pub signature: String,
91}
92
93/// Chatroom application object
94#[derive(Debug, Clone)]
95pub struct ChatroomObject {
96    id: SharedObjectId,
97    chatrooms: HashMap<String, Chatroom>,
98    users: HashMap<String, String>,
99    verifier: ECDSAVerifier,
100}
101
102impl ChatroomObject {
103    pub fn new() -> Self {
104        Self {
105            id: SharedObjectId::new(),
106            chatrooms: HashMap::new(),
107            users: HashMap::new(),
108            verifier: ECDSAVerifier::new(),
109        }
110    }
111
112    /// Get all chatrooms
113    pub fn get_chatrooms(&self) -> &HashMap<String, Chatroom> {
114        &self.chatrooms
115    }
116
117    /// Get a specific chatroom
118    pub fn get_chatroom(&self, name: &str) -> Option<&Chatroom> {
119        self.chatrooms.get(name)
120    }
121
122    /// Validate message signature
123    fn validate_signature(
124        &self,
125        msg_data: &Value,
126        signature: &str,
127        public_key_pem: &str,
128    ) -> Result<bool> {
129        // Extract message data without signature for verification
130        let mut msg_for_verification = msg_data.clone();
131        if let Some(obj) = msg_for_verification.as_object_mut() {
132            obj.remove("signature");
133        }
134
135        let payload = serde_json::to_string(&msg_for_verification).map_err(|e| {
136            ChaincraftError::Serialization(crate::error::SerializationError::Json(e))
137        })?;
138
139        // Decode signature from hex
140        let signature_bytes = hex::decode(signature)
141            .map_err(|_| ChaincraftError::validation("Invalid signature hex"))?;
142
143        // Verify signature
144        let ecdsa_sig = ECDSASignature::from_bytes(&signature_bytes)
145            .map_err(|_| ChaincraftError::validation("Invalid signature format"))?;
146
147        self.verifier
148            .verify(payload.as_bytes(), &ecdsa_sig, public_key_pem)
149    }
150
151    /// Check if timestamp is recent (within 15 seconds)
152    fn is_timestamp_recent(&self, timestamp: f64) -> bool {
153        let now = SystemTime::now()
154            .duration_since(UNIX_EPOCH)
155            .unwrap()
156            .as_secs_f64();
157
158        let diff = (now - timestamp).abs();
159        diff <= 15.0
160    }
161
162    /// Process a create chatroom message
163    async fn process_create_chatroom(
164        &mut self,
165        msg: ChatroomMessageType,
166        msg_data: &Value,
167    ) -> Result<bool> {
168        if let ChatroomMessageType::CreateChatroom {
169            chatroom_name,
170            public_key_pem,
171            timestamp,
172            signature,
173        } = msg
174        {
175            // Validate signature
176            if !self.validate_signature(msg_data, &signature, &public_key_pem)? {
177                return Ok(false);
178            }
179
180            // Check timestamp
181            if !self.is_timestamp_recent(timestamp) {
182                return Ok(false);
183            }
184
185            // Check if chatroom already exists
186            if self.chatrooms.contains_key(&chatroom_name) {
187                return Ok(false);
188            }
189
190            // Create new chatroom
191            let chatroom = Chatroom {
192                name: chatroom_name.clone(),
193                admin: public_key_pem.clone(),
194                members: vec![public_key_pem.clone()], // Admin is automatically a member
195                messages: Vec::new(),
196            };
197
198            self.chatrooms.insert(chatroom_name, chatroom);
199            tracing::info!("Created chatroom with admin: {}", public_key_pem);
200
201            Ok(true)
202        } else {
203            Ok(false)
204        }
205    }
206
207    /// Process a request join message
208    async fn process_request_join(
209        &mut self,
210        msg: ChatroomMessageType,
211        msg_data: &Value,
212    ) -> Result<bool> {
213        if let ChatroomMessageType::RequestJoin {
214            chatroom_name,
215            public_key_pem,
216            timestamp,
217            signature,
218        } = msg
219        {
220            // Validate signature
221            if !self.validate_signature(msg_data, &signature, &public_key_pem)? {
222                return Ok(false);
223            }
224
225            // Check timestamp
226            if !self.is_timestamp_recent(timestamp) {
227                return Ok(false);
228            }
229
230            // Check if chatroom exists
231            if !self.chatrooms.contains_key(&chatroom_name) {
232                return Ok(false);
233            }
234
235            // Add to pending requests (for now, just log)
236            tracing::info!(
237                "Join request for chatroom '{}' from: {}",
238                chatroom_name,
239                public_key_pem
240            );
241
242            // Store the request message
243            if let Some(chatroom) = self.chatrooms.get_mut(&chatroom_name) {
244                let chat_msg = ChatMessage {
245                    message_type: "REQUEST_JOIN".to_string(),
246                    chatroom_name: chatroom_name.clone(),
247                    public_key_pem: public_key_pem.clone(),
248                    text: None,
249                    requester_key_pem: None,
250                    timestamp,
251                    signature,
252                };
253                chatroom.messages.push(chat_msg);
254            }
255
256            Ok(true)
257        } else {
258            Ok(false)
259        }
260    }
261
262    /// Process an accept member message
263    async fn process_accept_member(
264        &mut self,
265        msg: ChatroomMessageType,
266        msg_data: &Value,
267    ) -> Result<bool> {
268        if let ChatroomMessageType::AcceptMember {
269            chatroom_name,
270            public_key_pem,
271            requester_key_pem,
272            timestamp,
273            signature,
274        } = msg
275        {
276            // Validate signature
277            if !self.validate_signature(msg_data, &signature, &public_key_pem)? {
278                return Ok(false);
279            }
280
281            // Check timestamp
282            if !self.is_timestamp_recent(timestamp) {
283                return Ok(false);
284            }
285
286            // Check if chatroom exists and sender is admin
287            if let Some(chatroom) = self.chatrooms.get_mut(&chatroom_name) {
288                if chatroom.admin != public_key_pem {
289                    return Ok(false); // Only admin can accept members
290                }
291
292                // Add member if not already present
293                if !chatroom.members.contains(&requester_key_pem) {
294                    chatroom.members.push(requester_key_pem.clone());
295                    tracing::info!(
296                        "Added member {} to chatroom '{}'",
297                        requester_key_pem,
298                        chatroom_name
299                    );
300                }
301
302                // Store the accept message
303                let chat_msg = ChatMessage {
304                    message_type: "ACCEPT_MEMBER".to_string(),
305                    chatroom_name: chatroom_name.clone(),
306                    public_key_pem: public_key_pem.clone(),
307                    text: None,
308                    requester_key_pem: Some(requester_key_pem),
309                    timestamp,
310                    signature,
311                };
312                chatroom.messages.push(chat_msg);
313
314                Ok(true)
315            } else {
316                Ok(false)
317            }
318        } else {
319            Ok(false)
320        }
321    }
322
323    /// Process a post message
324    async fn process_post_message(
325        &mut self,
326        msg: ChatroomMessageType,
327        msg_data: &Value,
328    ) -> Result<bool> {
329        if let ChatroomMessageType::PostMessage {
330            chatroom_name,
331            public_key_pem,
332            text,
333            timestamp,
334            signature,
335        } = msg
336        {
337            // Validate signature
338            if !self.validate_signature(msg_data, &signature, &public_key_pem)? {
339                return Ok(false);
340            }
341
342            // Check timestamp
343            if !self.is_timestamp_recent(timestamp) {
344                return Ok(false);
345            }
346
347            // Check if chatroom exists and sender is a member
348            if let Some(chatroom) = self.chatrooms.get_mut(&chatroom_name) {
349                if !chatroom.members.contains(&public_key_pem) {
350                    return Ok(false); // Only members can post messages
351                }
352
353                // Add the message
354                let chat_msg = ChatMessage {
355                    message_type: "POST_MESSAGE".to_string(),
356                    chatroom_name: chatroom_name.clone(),
357                    public_key_pem: public_key_pem.clone(),
358                    text: Some(text),
359                    requester_key_pem: None,
360                    timestamp,
361                    signature,
362                };
363                chatroom.messages.push(chat_msg);
364
365                tracing::info!("Message posted to '{}' by: {}", chatroom_name, public_key_pem);
366
367                Ok(true)
368            } else {
369                Ok(false)
370            }
371        } else {
372            Ok(false)
373        }
374    }
375}
376
377impl Default for ChatroomObject {
378    fn default() -> Self {
379        Self::new()
380    }
381}
382
383#[async_trait]
384impl ApplicationObject for ChatroomObject {
385    fn id(&self) -> &SharedObjectId {
386        &self.id
387    }
388
389    fn type_name(&self) -> &'static str {
390        "ChatroomObject"
391    }
392
393    async fn is_valid(&self, message: &SharedMessage) -> Result<bool> {
394        // Check if this is a chatroom message
395        let msg_result: std::result::Result<ChatroomMessageType, _> =
396            serde_json::from_value(message.data.clone());
397        Ok(msg_result.is_ok())
398    }
399
400    async fn add_message(&mut self, message: SharedMessage) -> Result<()> {
401        let msg: ChatroomMessageType =
402            serde_json::from_value(message.data.clone()).map_err(|e| {
403                ChaincraftError::Serialization(crate::error::SerializationError::Json(e))
404            })?;
405
406        let processed = match &msg {
407            ChatroomMessageType::CreateChatroom { .. } => {
408                self.process_create_chatroom(msg.clone(), &message.data)
409                    .await?
410            },
411            ChatroomMessageType::RequestJoin { .. } => {
412                self.process_request_join(msg.clone(), &message.data)
413                    .await?
414            },
415            ChatroomMessageType::AcceptMember { .. } => {
416                self.process_accept_member(msg.clone(), &message.data)
417                    .await?
418            },
419            ChatroomMessageType::PostMessage { .. } => {
420                self.process_post_message(msg.clone(), &message.data)
421                    .await?
422            },
423        };
424
425        if processed {
426            tracing::debug!("Successfully processed chatroom message: {:?}", msg);
427        } else {
428            tracing::warn!("Failed to process chatroom message: {:?}", msg);
429        }
430
431        Ok(())
432    }
433
434    fn is_merkleized(&self) -> bool {
435        false
436    }
437
438    async fn get_latest_digest(&self) -> Result<String> {
439        let mut hasher = Sha256::new();
440
441        for (room_name, chatroom) in &self.chatrooms {
442            hasher.update(room_name.as_bytes());
443            hasher.update(chatroom.messages.len().to_le_bytes());
444        }
445
446        Ok(hex::encode(hasher.finalize()))
447    }
448
449    async fn has_digest(&self, _digest: &str) -> Result<bool> {
450        Ok(false) // Simplified implementation
451    }
452
453    async fn is_valid_digest(&self, _digest: &str) -> Result<bool> {
454        Ok(true)
455    }
456
457    async fn add_digest(&mut self, _digest: String) -> Result<bool> {
458        Ok(true)
459    }
460
461    async fn gossip_messages(&self, _digest: Option<&str>) -> Result<Vec<SharedMessage>> {
462        Ok(Vec::new()) // Simplified implementation
463    }
464
465    async fn get_messages_since_digest(&self, _digest: &str) -> Result<Vec<SharedMessage>> {
466        Ok(Vec::new()) // Simplified implementation
467    }
468
469    async fn get_state(&self) -> Result<Value> {
470        let state = serde_json::json!({
471            "chatroom_count": self.chatrooms.len(),
472            "chatrooms": self.chatrooms.keys().collect::<Vec<_>>(),
473            "total_messages": self.chatrooms.values().map(|c| c.messages.len()).sum::<usize>()
474        });
475        Ok(state)
476    }
477
478    async fn reset(&mut self) -> Result<()> {
479        self.chatrooms.clear();
480        Ok(())
481    }
482
483    fn clone_box(&self) -> Box<dyn ApplicationObject> {
484        Box::new(self.clone())
485    }
486
487    fn as_any(&self) -> &dyn Any {
488        self
489    }
490
491    fn as_any_mut(&mut self) -> &mut dyn Any {
492        self
493    }
494}
495
496/// Helper functions for creating chatroom messages
497pub mod helpers {
498    use super::*;
499    use crate::crypto::ecdsa::ECDSASigner;
500
501    /// Create a create chatroom message
502    pub fn create_chatroom_message(chatroom_name: String, signer: &ECDSASigner) -> Result<Value> {
503        let timestamp = SystemTime::now()
504            .duration_since(UNIX_EPOCH)
505            .unwrap()
506            .as_secs_f64();
507
508        let public_key_pem = signer.get_public_key_pem()?;
509
510        let mut msg = serde_json::json!({
511            "message_type": "CREATE_CHATROOM",
512            "chatroom_name": chatroom_name,
513            "public_key_pem": public_key_pem,
514            "timestamp": timestamp
515        });
516
517        // Sign the message
518        let payload = serde_json::to_string(&msg).map_err(|e| {
519            ChaincraftError::Serialization(crate::error::SerializationError::Json(e))
520        })?;
521        let signature = signer.sign(payload.as_bytes())?;
522
523        msg["signature"] = serde_json::Value::String(hex::encode(signature.to_bytes()));
524
525        Ok(msg)
526    }
527
528    /// Create a post message
529    pub fn create_post_message(
530        chatroom_name: String,
531        text: String,
532        signer: &ECDSASigner,
533    ) -> Result<Value> {
534        let timestamp = SystemTime::now()
535            .duration_since(UNIX_EPOCH)
536            .unwrap()
537            .as_secs_f64();
538
539        let public_key_pem = signer.get_public_key_pem()?;
540
541        let mut msg = serde_json::json!({
542            "message_type": "POST_MESSAGE",
543            "chatroom_name": chatroom_name,
544            "public_key_pem": public_key_pem,
545            "text": text,
546            "timestamp": timestamp
547        });
548
549        // Sign the message
550        let payload = serde_json::to_string(&msg).map_err(|e| {
551            ChaincraftError::Serialization(crate::error::SerializationError::Json(e))
552        })?;
553        let signature = signer.sign(payload.as_bytes())?;
554
555        msg["signature"] = serde_json::Value::String(hex::encode(signature.to_bytes()));
556
557        Ok(msg)
558    }
559}