No central server, user-owned data, reverse-chronological feed. Rust core + Tauri desktop + Android app + plain HTML/CSS/JS frontend. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
61 lines
1.7 KiB
Rust
61 lines
1.7 KiB
Rust
use crate::types::{Post, PostId};
|
|
|
|
/// Compute the content ID (BLAKE3 hash) of a post.
|
|
/// This is deterministic: same post data always produces the same ID.
|
|
pub fn compute_post_id(post: &Post) -> PostId {
|
|
let bytes = serde_json::to_vec(post).expect("post serialization should not fail");
|
|
*blake3::hash(&bytes).as_bytes()
|
|
}
|
|
|
|
/// Verify that a post's content matches the claimed ID
|
|
pub fn verify_post_id(id: &PostId, post: &Post) -> bool {
|
|
&compute_post_id(post) == id
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn test_deterministic_hashing() {
|
|
let post = Post {
|
|
author: [1u8; 32],
|
|
content: "hello world".to_string(),
|
|
attachments: vec![],
|
|
timestamp_ms: 1000,
|
|
};
|
|
let id1 = compute_post_id(&post);
|
|
let id2 = compute_post_id(&post);
|
|
assert_eq!(id1, id2);
|
|
}
|
|
|
|
#[test]
|
|
fn test_different_content_different_id() {
|
|
let post1 = Post {
|
|
author: [1u8; 32],
|
|
content: "hello".to_string(),
|
|
attachments: vec![],
|
|
timestamp_ms: 1000,
|
|
};
|
|
let post2 = Post {
|
|
author: [1u8; 32],
|
|
content: "world".to_string(),
|
|
attachments: vec![],
|
|
timestamp_ms: 1000,
|
|
};
|
|
assert_ne!(compute_post_id(&post1), compute_post_id(&post2));
|
|
}
|
|
|
|
#[test]
|
|
fn test_verify() {
|
|
let post = Post {
|
|
author: [1u8; 32],
|
|
content: "test".to_string(),
|
|
attachments: vec![],
|
|
timestamp_ms: 1000,
|
|
};
|
|
let id = compute_post_id(&post);
|
|
assert!(verify_post_id(&id, &post));
|
|
assert!(!verify_post_id(&[0u8; 32], &post));
|
|
}
|
|
}
|