v0.3.5: Encrypted receipt & comment slots, message delivery indicators
Encrypted slots in BlobHeader:
- Private posts get noise-prefilled receipt slots (64B, 1 per participant)
and comment slots (256B, ceil(participants/3), expandable)
- Slot key derived from post CEK via BLAKE3 — only participants can read
- CDN relays propagate opaque encrypted bytes without decryption
- 3 new BlobHeaderDiffOps: WriteReceiptSlot, WriteCommentSlot, AddCommentSlots
Receipt system:
- States: empty(0), delivered(1), seen(2), reacted(3)
- Slot index = position in sorted participant NodeId list
- Author can pre-feed emoji reaction at creation time
- 6 new crypto tests for slot encrypt/decrypt/derivation
Node methods:
- write_receipt_slot, write_comment_slot with upstream+downstream propagation
- read_receipt_slots, read_comment_slots with CEK-based decryption
- get_post_cek_and_participants helper for both Encrypted and GroupEncrypted
IPC: write_message_receipt, write_message_comment, get_message_receipts,
get_message_comments
Frontend:
- DM chat bubbles show delivery indicators (check → double → blue → emoji)
- Opening conversation auto-marks incoming messages as seen
- React button on messages with emoji prompt
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
a41b11c0b8
commit
b7f2d369fa
9 changed files with 882 additions and 4 deletions
|
|
@ -5516,6 +5516,62 @@ impl ConnectionManager {
|
|||
parent_post_id: payload.post_id,
|
||||
});
|
||||
}
|
||||
BlobHeaderDiffOp::WriteReceiptSlot { post_id, slot_index, data } => {
|
||||
// Store encrypted bytes directly — no decryption needed on relay nodes
|
||||
if let Ok(Some((json, _ts))) = storage.get_blob_header(post_id) {
|
||||
if let Ok(mut header) = serde_json::from_str::<crate::types::BlobHeader>(&json) {
|
||||
let idx = *slot_index as usize;
|
||||
while header.receipt_slots.len() <= idx {
|
||||
header.receipt_slots.push(vec![0u8; 64]); // padding
|
||||
}
|
||||
header.receipt_slots[idx] = data.clone();
|
||||
let now_ms = std::time::SystemTime::now()
|
||||
.duration_since(std::time::UNIX_EPOCH)
|
||||
.map(|d| d.as_millis() as u64)
|
||||
.unwrap_or(0);
|
||||
header.updated_at = now_ms;
|
||||
if let Ok(new_json) = serde_json::to_string(&header) {
|
||||
let _ = storage.store_blob_header(post_id, &header.author, &new_json, now_ms);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
BlobHeaderDiffOp::WriteCommentSlot { post_id, slot_index, data } => {
|
||||
if let Ok(Some((json, _ts))) = storage.get_blob_header(post_id) {
|
||||
if let Ok(mut header) = serde_json::from_str::<crate::types::BlobHeader>(&json) {
|
||||
let idx = *slot_index as usize;
|
||||
while header.comment_slots.len() <= idx {
|
||||
header.comment_slots.push(vec![0u8; 256]);
|
||||
}
|
||||
header.comment_slots[idx] = data.clone();
|
||||
let now_ms = std::time::SystemTime::now()
|
||||
.duration_since(std::time::UNIX_EPOCH)
|
||||
.map(|d| d.as_millis() as u64)
|
||||
.unwrap_or(0);
|
||||
header.updated_at = now_ms;
|
||||
if let Ok(new_json) = serde_json::to_string(&header) {
|
||||
let _ = storage.store_blob_header(post_id, &header.author, &new_json, now_ms);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
BlobHeaderDiffOp::AddCommentSlots { post_id, count: _, slots } => {
|
||||
if let Ok(Some((json, _ts))) = storage.get_blob_header(post_id) {
|
||||
if let Ok(mut header) = serde_json::from_str::<crate::types::BlobHeader>(&json) {
|
||||
for slot in slots {
|
||||
header.comment_slots.push(slot.clone());
|
||||
}
|
||||
let now_ms = std::time::SystemTime::now()
|
||||
.duration_since(std::time::UNIX_EPOCH)
|
||||
.map(|d| d.as_millis() as u64)
|
||||
.unwrap_or(0);
|
||||
header.updated_at = now_ms;
|
||||
if let Ok(new_json) = serde_json::to_string(&header) {
|
||||
let _ = storage.store_blob_header(post_id, &header.author, &new_json, now_ms);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
BlobHeaderDiffOp::Unknown => {} // future ops — silently skip
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue