1use crate::auth::Sig;
18use crate::fwid::{Key, compute_key, fw_check, fw_to_key};
19use crate::types::{
20 Device, DeviceId, Endpoint, Identity, IdentityHandle, MlDsaKeyPair, Presence, PresenceReceipt,
21 StorageHandle, StorageStrategy,
22};
23use anyhow::{Context, Result};
24use serde::{Deserialize, Serialize};
25use std::collections::HashMap;
26use std::sync::Arc;
27use tokio::sync::RwLock;
28struct MockDht {
32 storage: HashMap<Key, Vec<u8>>,
33}
34
35impl MockDht {
36 fn new() -> Self {
37 Self {
38 storage: HashMap::new(),
39 }
40 }
41
42 async fn put(&mut self, key: Key, value: Vec<u8>) -> Result<()> {
43 self.storage.insert(key, value);
44 Ok(())
45 }
46
47 async fn get(&self, key: &Key) -> Result<Vec<u8>> {
48 self.storage
49 .get(key)
50 .cloned()
51 .ok_or_else(|| anyhow::anyhow!("Key not found"))
52 }
53}
54
55static DHT: once_cell::sync::Lazy<Arc<RwLock<MockDht>>> =
57 once_cell::sync::Lazy::new(|| Arc::new(RwLock::new(MockDht::new())));
58
59static GLOBAL_DHT_CLIENT: once_cell::sync::OnceCell<Arc<crate::dht::client::DhtClient>> =
61 once_cell::sync::OnceCell::new();
62
63pub fn set_dht_client(client: crate::dht::client::DhtClient) -> bool {
65 GLOBAL_DHT_CLIENT.set(Arc::new(client)).is_ok()
66}
67
68fn get_dht_client() -> Option<Arc<crate::dht::client::DhtClient>> {
69 GLOBAL_DHT_CLIENT.get().cloned()
70}
71
72async fn dht_put_bytes(key: &Key, value: Vec<u8>) -> Result<()> {
73 if let Some(client) = get_dht_client() {
74 let k = hex::encode(key.as_bytes());
75 let _ = client
76 .put(k, value)
77 .await
78 .context("Failed to store data in DHT client")?;
79 Ok(())
80 } else {
81 let mut dht = DHT.write().await;
82 dht.put(key.clone(), value).await
83 }
84}
85
86async fn dht_get_bytes(key: &Key) -> Result<Vec<u8>> {
87 if let Some(client) = get_dht_client() {
88 let k = hex::encode(key.as_bytes());
89 match client.get(k).await.context("DHT get failed")? {
90 Some(v) => Ok(v),
91 None => anyhow::bail!("Key not found"),
92 }
93 } else {
94 let dht = DHT.read().await;
95 dht.get(key).await
96 }
97}
98
99#[derive(Debug, Clone, Serialize, Deserialize)]
105pub struct IdentityPacketV1 {
106 pub v: u8,
107 pub words: [String; 4],
108 pub id: Key,
109 pub pk: Vec<u8>,
110 pub sig: Option<Vec<u8>>, pub device_set_root: Key,
112}
113
114#[derive(Debug, Clone, Serialize, Deserialize)]
116pub struct MemberRef {
117 pub member_id: Key,
118 pub member_pk: Vec<u8>,
119}
120
121#[derive(Debug, Clone, Serialize, Deserialize)]
123pub struct GroupIdentityPacketV1 {
124 pub v: u8,
125 pub words: [String; 4],
126 pub id: Key,
127 pub group_pk: Vec<u8>,
128 pub group_sig: Vec<u8>,
129 pub members: Vec<MemberRef>,
130 pub membership_root: Key,
131 pub created_at: u64,
132 pub mls_ciphersuite: Option<u16>,
133}
134
135#[derive(Clone)]
137pub struct GroupKeyPair {
138 pub group_pk: crate::quantum_crypto::MlDsaPublicKey,
139 pub group_sk: crate::quantum_crypto::MlDsaSecretKey,
140}
141
142impl std::fmt::Debug for GroupKeyPair {
143 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
144 write!(
145 f,
146 "GroupKeyPair {{ group_pk: <{} bytes>, group_sk: <hidden> }}",
147 self.group_pk.as_bytes().len()
148 )
149 }
150}
151
152pub async fn register_identity(words: [&str; 4], keypair: &MlDsaKeyPair) -> Result<IdentityHandle> {
165 let words_owned: [String; 4] = [
167 words[0].to_string(),
168 words[1].to_string(),
169 words[2].to_string(),
170 words[3].to_string(),
171 ];
172
173 if !fw_check(words_owned.clone()) {
175 anyhow::bail!("Invalid word in identity");
176 }
177
178 let key = fw_to_key(words_owned.clone())?;
180
181 let dht = DHT.read().await;
183 if dht.get(&key).await.is_ok() {
184 anyhow::bail!("Identity already registered");
185 }
186 drop(dht);
187
188 let identity = Identity {
190 words: words_owned.clone(),
191 key: key.clone(),
192 public_key: keypair.public_key.clone(),
193 };
194
195 let packet = IdentityPacketV1 {
196 v: 1,
197 words: words_owned.clone(),
198 id: key.clone(),
199 pk: keypair.public_key.clone(),
200 sig: None,
201 device_set_root: compute_key("device-set", key.as_bytes()),
202 };
203
204 dht_put_bytes(&key, serde_json::to_vec(&packet)?).await?;
205
206 Ok(IdentityHandle::new(identity, keypair.clone()))
207}
208
209pub async fn get_identity(key: Key) -> Result<Identity> {
217 let data = dht_get_bytes(&key).await.context("Identity not found")?;
219 if let Ok(pkt) = serde_json::from_slice::<IdentityPacketV1>(&data) {
220 let identity = Identity {
221 words: pkt.words,
222 key: pkt.id,
223 public_key: pkt.pk,
224 };
225 return Ok(identity);
226 }
227 let identity: Identity = serde_json::from_slice(&data)?;
229 Ok(identity)
230}
231
232pub async fn identity_fetch(key: Key) -> Result<IdentityPacketV1> {
234 let data = dht_get_bytes(&key).await.context("Identity not found")?;
235 let pkt: IdentityPacketV1 = serde_json::from_slice(&data)?;
236 Ok(pkt)
237}
238
239pub async fn register_presence(
253 handle: &IdentityHandle,
254 devices: Vec<Device>,
255 active_device: DeviceId,
256) -> Result<PresenceReceipt> {
257 if !devices.iter().any(|d| d.id == active_device) {
259 anyhow::bail!("Active device not in device list");
260 }
261
262 let presence = Presence {
264 identity: handle.key(),
265 devices,
266 active_device: Some(active_device),
267 timestamp: std::time::SystemTime::now()
268 .duration_since(std::time::UNIX_EPOCH)?
269 .as_secs(),
270 signature: vec![], };
272
273 let presence_bytes = serde_json::to_vec(&presence)?;
275 let signature = handle.sign(&presence_bytes)?;
276
277 let mut signed_presence = presence;
278 signed_presence.signature = signature;
279
280 let presence_key = derive_presence_key(handle.key());
282 let mut dht = DHT.write().await;
283 dht.put(presence_key, serde_json::to_vec(&signed_presence)?)
284 .await?;
285
286 let receipt = PresenceReceipt {
288 identity: handle.key(),
289 timestamp: signed_presence.timestamp,
290 storing_nodes: vec![Key::from([0u8; 32])], };
292
293 Ok(receipt)
294}
295
296pub async fn get_presence(identity_key: Key) -> Result<Presence> {
304 let presence_key = derive_presence_key(identity_key);
305 let dht = DHT.read().await;
306 let data = dht.get(&presence_key).await.context("Presence not found")?;
307 let presence: Presence = serde_json::from_slice(&data)?;
308 Ok(presence)
309}
310
311pub async fn register_headless(
321 handle: &IdentityHandle,
322 storage_gb: u32,
323 endpoint: Endpoint,
324) -> Result<DeviceId> {
325 let mut presence = get_presence(handle.key()).await?;
327
328 let device = Device {
330 id: DeviceId::generate(),
331 device_type: crate::types::presence::DeviceType::Headless,
332 storage_gb: storage_gb as u64,
333 endpoint,
334 capabilities: crate::types::presence::DeviceCapabilities {
335 storage_bytes: storage_gb as u64 * 1_000_000_000,
336 always_online: true,
337 supports_fec: true,
338 supports_seal: true,
339 ..Default::default()
340 },
341 };
342
343 let device_id = device.id;
344 presence.devices.push(device);
345
346 let active = presence.active_device.unwrap_or(device_id);
348 register_presence(handle, presence.devices, active).await?;
349
350 Ok(device_id)
351}
352
353pub async fn set_active_device(handle: &IdentityHandle, device_id: DeviceId) -> Result<()> {
359 let presence = get_presence(handle.key()).await?;
361
362 if !presence.devices.iter().any(|d| d.id == device_id) {
364 anyhow::bail!("Device not found in presence");
365 }
366
367 register_presence(handle, presence.devices, device_id).await?;
369 Ok(())
370}
371
372pub async fn store_data(
386 handle: &IdentityHandle,
387 data: Vec<u8>,
388 group_size: usize,
389) -> Result<StorageHandle> {
390 let strategy = StorageStrategy::from_group_size(group_size);
392
393 match strategy {
394 StorageStrategy::Direct => store_direct(handle, data).await,
395 StorageStrategy::FullReplication { replicas } => {
396 store_replicated(handle, data, replicas).await
397 }
398 StorageStrategy::FecEncoded {
399 data_shards,
400 parity_shards,
401 ..
402 } => store_with_fec(handle, data, data_shards, parity_shards).await,
403 }
404}
405
406pub async fn store_dyad(
416 handle1: &IdentityHandle,
417 _handle2_key: Key,
418 data: Vec<u8>,
419) -> Result<StorageHandle> {
420 store_replicated(handle1, data, 2).await
422}
423
424pub async fn store_with_fec(
435 handle: &IdentityHandle,
436 data: Vec<u8>,
437 data_shards: usize,
438 parity_shards: usize,
439) -> Result<StorageHandle> {
440 let storage_id = Key::from(*blake3::hash(&data).as_bytes());
442
443 let mut shard_map = crate::types::storage::ShardMap::new();
448
449 let presence = get_presence(handle.key()).await?;
451
452 let mut devices = presence.devices.clone();
454 devices.sort_by_key(|d| match d.device_type {
455 crate::types::presence::DeviceType::Headless => 0,
456 crate::types::presence::DeviceType::Active => 1,
457 crate::types::presence::DeviceType::Mobile => 2,
458 });
459
460 let total_shards = data_shards + parity_shards;
462 for (i, device) in devices.iter().take(total_shards).enumerate() {
463 shard_map.assign_shard(device.id, i as u32);
464 }
465
466 let mut dht = DHT.write().await;
468 dht.put(storage_id.clone(), data.clone()).await?;
469
470 let handle = StorageHandle {
472 id: storage_id,
473 size: data.len() as u64,
474 strategy: StorageStrategy::FecEncoded {
475 data_shards,
476 parity_shards,
477 shard_size: 65536,
478 },
479 shard_map,
480 sealed_key: Some(vec![0u8; 32]), };
482
483 Ok(handle)
484}
485
486pub async fn get_data(handle: &StorageHandle) -> Result<Vec<u8>> {
494 let dht = DHT.read().await;
498 let data = dht.get(&handle.id).await.context("Data not found")?;
499 Ok(data)
500}
501
502fn derive_presence_key(identity_key: Key) -> Key {
508 let mut hasher = blake3::Hasher::new();
509 hasher.update(b"presence:");
510 hasher.update(identity_key.as_bytes());
511 Key::from(*hasher.finalize().as_bytes())
512}
513
514async fn store_direct(handle: &IdentityHandle, data: Vec<u8>) -> Result<StorageHandle> {
516 let storage_id = Key::from(*blake3::hash(&data).as_bytes());
517
518 let mut dht = DHT.write().await;
520 dht.put(storage_id.clone(), data.clone()).await?;
521
522 let presence = get_presence(handle.key()).await?;
524 let device = presence.devices.first().context("No devices available")?;
525
526 let mut shard_map = crate::types::storage::ShardMap::new();
527 shard_map.assign_shard(device.id, 0);
528
529 Ok(StorageHandle {
530 id: storage_id,
531 size: data.len() as u64,
532 strategy: StorageStrategy::Direct,
533 shard_map,
534 sealed_key: Some(vec![0u8; 32]),
535 })
536}
537
538pub fn group_identity_canonical_sign_bytes(id: &Key, membership_root: &Key) -> Vec<u8> {
544 let mut out = Vec::with_capacity(16 + 32 + 32);
545 out.extend_from_slice(b"saorsa-group:identity:v1");
546 out.extend_from_slice(id.as_bytes());
547 out.extend_from_slice(membership_root.as_bytes());
548 out
549}
550
551fn compute_membership_root(members: &[MemberRef]) -> Key {
552 let mut ids: Vec<[u8; 32]> = members.iter().map(|m| *m.member_id.as_bytes()).collect();
553 ids.sort_unstable();
554 let mut hasher = blake3::Hasher::new();
555 for id in ids {
556 hasher.update(&id);
557 }
558 Key::from(*hasher.finalize().as_bytes())
559}
560
561pub fn group_identity_create(
563 words: [String; 4],
564 members: Vec<MemberRef>,
565) -> Result<(GroupIdentityPacketV1, GroupKeyPair)> {
566 if !fw_check(words.clone()) {
568 anyhow::bail!("Invalid group words");
569 }
570 let id = fw_to_key(words.clone())?;
571
572 use crate::quantum_crypto::{MlDsa65, MlDsaOperations};
574 let ml = MlDsa65::new();
575 let (group_pk, group_sk) = ml
576 .generate_keypair()
577 .map_err(|e| anyhow::anyhow!("group keypair generation failed: {e:?}"))?;
578
579 let membership_root = compute_membership_root(&members);
581 let msg = group_identity_canonical_sign_bytes(&id, &membership_root);
582 let sig = ml
583 .sign(&group_sk, &msg)
584 .map_err(|e| anyhow::anyhow!("group sign failed: {e:?}"))?;
585
586 let pkt = GroupIdentityPacketV1 {
587 v: 1,
588 words,
589 id: id.clone(),
590 group_pk: group_pk.as_bytes().to_vec(),
591 group_sig: sig.0.to_vec(),
592 members,
593 membership_root,
594 created_at: std::time::SystemTime::now()
595 .duration_since(std::time::UNIX_EPOCH)
596 .unwrap_or_default()
597 .as_secs(),
598 mls_ciphersuite: None,
599 };
600
601 Ok((pkt, GroupKeyPair { group_pk, group_sk }))
602}
603
604pub async fn group_identity_publish(packet: GroupIdentityPacketV1) -> Result<()> {
606 let root = compute_membership_root(&packet.members);
608 if root != packet.membership_root {
609 anyhow::bail!("membership_root mismatch");
610 }
611 use crate::quantum_crypto::{MlDsa65, MlDsaOperations, MlDsaPublicKey, MlDsaSignature};
613 const SIG_LEN: usize = 3309;
614 if packet.group_sig.len() != SIG_LEN {
615 anyhow::bail!("invalid signature length");
616 }
617 let mut sig_arr = [0u8; SIG_LEN];
618 sig_arr.copy_from_slice(&packet.group_sig);
619 let sig = MlDsaSignature(Box::new(sig_arr));
620 let pk = MlDsaPublicKey::from_bytes(&packet.group_pk)
621 .map_err(|_| anyhow::anyhow!("invalid group_pk"))?;
622 let ml = MlDsa65::new();
623 let msg = group_identity_canonical_sign_bytes(&packet.id, &packet.membership_root);
624 let ok = ml
625 .verify(&pk, &msg, &sig)
626 .map_err(|e| anyhow::anyhow!("verify failed: {e:?}"))?;
627 if !ok {
628 anyhow::bail!("group signature invalid");
629 }
630 dht_put_bytes(&packet.id, serde_json::to_vec(&packet)?).await
631}
632
633pub async fn group_identity_fetch(id_key: Key) -> Result<GroupIdentityPacketV1> {
635 let data = dht_get_bytes(&id_key).await.context("Group not found")?;
636 let pkt: GroupIdentityPacketV1 = serde_json::from_slice(&data)?;
637 Ok(pkt)
638}
639
640pub async fn group_identity_update_members_signed(
642 id_key: Key,
643 new_members: Vec<MemberRef>,
644 group_pk: Vec<u8>,
645 group_sig: Sig,
646) -> Result<()> {
647 let new_root = compute_membership_root(&new_members);
649 use crate::quantum_crypto::{MlDsa65, MlDsaOperations, MlDsaPublicKey, MlDsaSignature};
650 const SIG_LEN: usize = 3309;
651 let sig_bytes = group_sig.as_bytes();
652 if sig_bytes.len() != SIG_LEN {
653 anyhow::bail!("invalid signature length");
654 }
655 let mut sig_arr = [0u8; SIG_LEN];
656 sig_arr.copy_from_slice(sig_bytes);
657 let sig = MlDsaSignature(Box::new(sig_arr));
658 let pk =
659 MlDsaPublicKey::from_bytes(&group_pk).map_err(|_| anyhow::anyhow!("invalid group_pk"))?;
660 let ml = MlDsa65::new();
661 let msg = group_identity_canonical_sign_bytes(&id_key, &new_root);
662 let ok = ml
663 .verify(&pk, &msg, &sig)
664 .map_err(|e| anyhow::anyhow!("verify failed: {e:?}"))?;
665 if !ok {
666 anyhow::bail!("group signature invalid");
667 }
668
669 let mut pkt = match group_identity_fetch(id_key.clone()).await {
671 Ok(p) => p,
672 Err(_) => GroupIdentityPacketV1 {
673 v: 1,
674 words: [String::new(), String::new(), String::new(), String::new()],
675 id: id_key.clone(),
676 group_pk: group_pk.clone(),
677 group_sig: sig.0.clone().to_vec(),
678 members: Vec::new(),
679 membership_root: new_root.clone(),
680 created_at: std::time::SystemTime::now()
681 .duration_since(std::time::UNIX_EPOCH)
682 .unwrap_or_default()
683 .as_secs(),
684 mls_ciphersuite: None,
685 },
686 };
687
688 pkt.members = new_members;
689 pkt.membership_root = new_root;
690 pkt.group_pk = group_pk;
691 pkt.group_sig = sig.0.to_vec();
692
693 group_identity_publish(pkt).await
694}
695
696async fn store_replicated(
698 handle: &IdentityHandle,
699 data: Vec<u8>,
700 replicas: usize,
701) -> Result<StorageHandle> {
702 let storage_id = Key::from(*blake3::hash(&data).as_bytes());
703
704 let mut dht = DHT.write().await;
706 dht.put(storage_id.clone(), data.clone()).await?;
707
708 let presence = get_presence(handle.key()).await?;
710 let mut shard_map = crate::types::storage::ShardMap::new();
711
712 for (i, device) in presence.devices.iter().take(replicas).enumerate() {
713 shard_map.assign_shard(device.id, i as u32);
714 }
715
716 Ok(StorageHandle {
717 id: storage_id,
718 size: data.len() as u64,
719 strategy: StorageStrategy::FullReplication { replicas },
720 shard_map,
721 sealed_key: Some(vec![0u8; 32]),
722 })
723}
724
725use crate::entities::{
730 ENTITY_REGISTRY, Entity, EntityHandle, EntityId, EntityInfo, EntitySettings, EntityType,
731 FourWordAddress, create_entity as internal_create_entity,
732};
733
734pub async fn create_entity(
757 entity_type: EntityType,
758 name: &str,
759 four_words: [&str; 4],
760 description: Option<String>,
761 settings: Option<EntitySettings>,
762) -> Result<EntityHandle> {
763 let words = [
765 four_words[0].to_string(),
766 four_words[1].to_string(),
767 four_words[2].to_string(),
768 four_words[3].to_string(),
769 ];
770
771 if !fw_check(words.clone()) {
773 anyhow::bail!("Invalid four-word address");
774 }
775
776 let mut entity = internal_create_entity(entity_type, name.to_string(), words).await?;
778
779 if let Some(desc) = description {
781 entity.core.metadata.description = Some(desc);
782 }
783 if let Some(custom_settings) = settings {
784 entity.core.metadata.settings = custom_settings;
785 }
786
787 ENTITY_REGISTRY.register(entity).await
789}
790
791pub async fn get_entity(id: &EntityId) -> Option<Arc<Entity>> {
799 ENTITY_REGISTRY.get(id).await
800}
801
802pub async fn get_entity_by_address(four_words: [&str; 4]) -> Option<Arc<Entity>> {
810 let words = [
811 four_words[0].to_string(),
812 four_words[1].to_string(),
813 four_words[2].to_string(),
814 four_words[3].to_string(),
815 ];
816
817 match FourWordAddress::from_words(words) {
818 Ok(address) => ENTITY_REGISTRY.get_by_address(&address).await,
819 Err(_) => None,
820 }
821}
822
823pub async fn list_entities() -> Vec<EntityInfo> {
828 ENTITY_REGISTRY.list().await
829}
830
831pub async fn entity_disk_write(
842 entity_handle: &EntityHandle,
843 path: &str,
844 content: Vec<u8>,
845 is_public: bool,
846) -> Result<()> {
847 use crate::virtual_disk::{FileMetadata, disk_write};
848
849 let disk = if is_public {
850 entity_handle.public_disk()
851 } else {
852 entity_handle.private_disk()
853 };
854
855 let metadata = FileMetadata {
856 mime_type: Some("application/octet-stream".to_string()),
857 attributes: HashMap::new(),
858 permissions: 0o644,
859 };
860
861 disk_write(disk, path, &content, metadata).await?;
862 Ok(())
863}
864
865pub async fn entity_disk_read(
875 entity_handle: &EntityHandle,
876 path: &str,
877 is_public: bool,
878) -> Result<Vec<u8>> {
879 use crate::virtual_disk::disk_read;
880
881 let disk = if is_public {
882 entity_handle.public_disk()
883 } else {
884 entity_handle.private_disk()
885 };
886
887 disk_read(disk, path).await
888}
889
890pub async fn entity_disk_list(
901 entity_handle: &EntityHandle,
902 path: &str,
903 recursive: bool,
904 is_public: bool,
905) -> Result<Vec<crate::virtual_disk::FileEntry>> {
906 use crate::virtual_disk::disk_list;
907
908 let disk = if is_public {
909 entity_handle.public_disk()
910 } else {
911 entity_handle.private_disk()
912 };
913
914 disk_list(disk, path, recursive).await
915}
916
917pub async fn entity_set_website(_entity_handle: &EntityHandle, _enabled: bool) -> Result<()> {
926 Ok(())
932}
933
934pub fn entity_website_url(entity_handle: &EntityHandle) -> Option<String> {
942 entity_handle.website_url()
943}