trust-store 0.1.0

Web of Trust with TOFU key verification and social graph attestation for P2P networks
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
//! # trust-store
//!
//! Web of Trust with TOFU (Trust-On-First-Use) key verification and social graph
//! attestation for P2P networks.
//!
//! ## Features
//!
//! - **TOFU key pinning** — pin a peer's public key on first contact
//! - **Key change detection** — flag potential MITM attacks
//! - **Social graph attestation** — peers vouch for each other's keys
//! - **Computed trust scores** — bubble trust through the graph with decay
//! - **Ed25519 signature verification** — cryptographically verify attestations
//!
//! ## Quick Start
//!
//! ```rust
//! use trust_store::TrustStore;
//!
//! # #[tokio::main]
//! # async fn main() {
//! let store = TrustStore::new();
//! let key = vec![0x01u8; 32];
//!
//! // First contact — TOFU pin
//! let is_new = store.verify_or_pin("peer1", &key).await.unwrap();
//! assert!(is_new);
//!
//! // Second contact with same key — trusted
//! let is_new = store.verify_or_pin("peer1", &key).await.unwrap();
//! assert!(!is_new);
//! # }
//! ```
//!
//! ## No PKI, No CA, No Blockchain
//!
//! Pure peer attestation — like SSH known_hosts + web of trust combined.

#![warn(missing_docs)]

use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::sync::Arc;
use thiserror::Error;
use tokio::sync::RwLock;

/// Errors that can occur during trust store operations.
#[derive(Debug, Error)]
#[allow(missing_docs)]
pub enum TrustError {
    /// Key has changed since first contact — possible MITM
    #[error("Key change detected for peer {peer_id}")]
    KeyChanged { peer_id: String },
    /// Unknown peer
    #[error("Unknown peer: {peer_id}")]
    UnknownPeer { peer_id: String },
    /// Attestation verification failed
    #[error("Attestation verification failed: {0}")]
    AttestationFailed(String),
    /// Key mismatch during verification
    #[error("Key mismatch during verification")]
    KeyMismatch,
}

/// Result type for trust store operations
pub type TrustResult<T> = Result<T, TrustError>;

/// Trust level for a known peer
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum TrustLevel {
    /// Never seen before
    Unknown,
    /// First contact — key pinned but not verified
    FirstContact,
    /// Key matches what we've seen before
    Trusted,
    /// Someone we trust has vouched for this peer
    Vouched,
    /// We have met this peer out-of-band (QR scan, in person)
    Verified,
    /// Key has changed since we first saw it — possible MITM
    KeyChanged,
    /// Explicitly blocked by the user
    Blocked,
}

impl TrustLevel {
    /// Numeric score for this trust level
    pub fn score(&self) -> f32 {
        match self {
            Self::Unknown => 0.0,
            Self::FirstContact => 0.3,
            Self::Trusted => 0.6,
            Self::Vouched => 0.7,
            Self::Verified => 1.0,
            Self::KeyChanged => -1.0,
            Self::Blocked => -2.0,
        }
    }

    /// Whether this trust level is usable for communication
    pub fn is_usable(&self) -> bool {
        !matches!(self, Self::Blocked | Self::KeyChanged)
    }
}

/// A vouching attestation from one peer about another
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Attestation {
    /// The peer making the claim
    pub attester_id: String,
    /// The peer being attested
    pub subject_id: String,
    /// The subject's public key being attested
    pub subject_pub_key: Vec<u8>,
    /// Ed25519 signature from attester
    pub signature: Vec<u8>,
    /// Unix timestamp
    pub timestamp: u64,
    /// Optional human-readable note
    pub note: Option<String>,
}

/// Our record of a known peer
#[derive(Debug, Clone, Serialize, Deserialize)]
#[allow(missing_docs)]
pub struct PeerRecord {
    pub peer_id: String,
    /// The first public key we saw from this peer (pinned)
    pub pinned_key: Vec<u8>,
    /// The most recently seen key
    pub current_key: Vec<u8>,
    pub trust_level: TrustLevel,
    pub first_seen: u64,
    pub last_seen: u64,
    pub attestations: Vec<Attestation>,
    pub interaction_count: u32,
    pub display_name: Option<String>,
}

impl PeerRecord {
    /// Whether the key has changed since first contact
    pub fn has_key_changed(&self) -> bool {
        self.pinned_key != self.current_key
    }

    /// Compute trust score incorporating attestations and interaction history
    pub fn computed_trust_score(&self, our_attesters: &HashMap<String, f32>) -> f32 {
        let base = self.trust_level.score();
        if base < 0.0 {
            return base;
        }

        let attestation_bonus: f32 = self
            .attestations
            .iter()
            .filter_map(|a| our_attesters.get(&a.attester_id))
            .map(|&attester_score| attester_score * 0.3)
            .sum::<f32>()
            .min(0.3);

        let interaction_bonus = (self.interaction_count as f32 / 100.0).min(0.1);

        (base + attestation_bonus + interaction_bonus).min(1.0)
    }
}

/// The trust store — manages all known peer records
pub struct TrustStore {
    peers: Arc<RwLock<HashMap<String, PeerRecord>>>,
    our_trust_scores: Arc<RwLock<HashMap<String, f32>>>,
}

impl TrustStore {
    /// Create a new empty trust store
    pub fn new() -> Self {
        Self {
            peers: Arc::new(RwLock::new(HashMap::new())),
            our_trust_scores: Arc::new(RwLock::new(HashMap::new())),
        }
    }

    /// Called when we first encounter a peer's public key (TOFU).
    ///
    /// Returns `Ok(true)` if this is a new peer, `Ok(false)` if key matches.
    /// Returns `Err` if key has changed for a known peer.
    pub async fn verify_or_pin(&self, peer_id: &str, public_key: &[u8]) -> TrustResult<bool> {
        let mut peers = self.peers.write().await;

        if let Some(record) = peers.get_mut(peer_id) {
            record.current_key = public_key.to_vec();
            record.last_seen = now_secs();
            record.interaction_count += 1;

            if record.pinned_key != public_key {
                record.trust_level = TrustLevel::KeyChanged;
                return Err(TrustError::KeyChanged {
                    peer_id: peer_id.to_string(),
                });
            }

            if record.trust_level == TrustLevel::FirstContact {
                record.trust_level = TrustLevel::Trusted;
            }

            Ok(false)
        } else {
            peers.insert(
                peer_id.to_string(),
                PeerRecord {
                    peer_id: peer_id.to_string(),
                    pinned_key: public_key.to_vec(),
                    current_key: public_key.to_vec(),
                    trust_level: TrustLevel::FirstContact,
                    first_seen: now_secs(),
                    last_seen: now_secs(),
                    attestations: vec![],
                    interaction_count: 1,
                    display_name: None,
                },
            );
            Ok(true)
        }
    }

    /// Record an attestation from a trusted peer about another peer
    pub async fn add_attestation(&self, attestation: Attestation) -> TrustResult<()> {
        self.verify_attestation_signature(&attestation)
            .await
            .map_err(TrustError::AttestationFailed)?;

        let mut peers = self.peers.write().await;
        let record = peers
            .entry(attestation.subject_id.clone())
            .or_insert_with(|| PeerRecord {
                peer_id: attestation.subject_id.clone(),
                pinned_key: attestation.subject_pub_key.clone(),
                current_key: attestation.subject_pub_key.clone(),
                trust_level: TrustLevel::Vouched,
                first_seen: now_secs(),
                last_seen: now_secs(),
                attestations: vec![],
                interaction_count: 0,
                display_name: None,
            });

        if !record
            .attestations
            .iter()
            .any(|a| a.attester_id == attestation.attester_id)
        {
            record.attestations.push(attestation);

            if record.trust_level == TrustLevel::Unknown
                || record.trust_level == TrustLevel::FirstContact
            {
                record.trust_level = TrustLevel::Vouched;
            }
        }

        Ok(())
    }

    /// Upgrade a peer to Verified status (after out-of-band verification)
    pub async fn mark_verified(&self, peer_id: &str, verified_key: &[u8]) -> TrustResult<()> {
        let mut peers = self.peers.write().await;
        let record = peers
            .get_mut(peer_id)
            .ok_or_else(|| TrustError::UnknownPeer {
                peer_id: peer_id.to_string(),
            })?;

        if record.pinned_key != verified_key {
            return Err(TrustError::KeyMismatch);
        }

        record.trust_level = TrustLevel::Verified;
        Ok(())
    }

    /// Block a peer
    pub async fn block(&self, peer_id: &str) {
        let mut peers = self.peers.write().await;
        if let Some(record) = peers.get_mut(peer_id) {
            record.trust_level = TrustLevel::Blocked;
        }
    }

    /// Get trust info for a peer
    pub async fn get(&self, peer_id: &str) -> Option<PeerRecord> {
        self.peers.read().await.get(peer_id).cloned()
    }

    /// Get trust score (0.0–1.0, or negative for warnings)
    pub async fn trust_score(&self, peer_id: &str) -> f32 {
        let peers = self.peers.read().await;
        let scores = self.our_trust_scores.read().await;
        peers
            .get(peer_id)
            .map(|r| r.computed_trust_score(&scores))
            .unwrap_or(0.0)
    }

    /// List all known peers, sorted by trust score descending
    pub async fn all_peers(&self) -> Vec<PeerRecord> {
        let peers = self.peers.read().await;
        let scores = self.our_trust_scores.read().await;
        let mut list: Vec<_> = peers.values().cloned().collect();
        list.sort_by(|a, b| {
            b.computed_trust_score(&scores)
                .partial_cmp(&a.computed_trust_score(&scores))
                .unwrap_or(std::cmp::Ordering::Equal)
        });
        list
    }

    /// Set our trust score for a peer
    pub async fn set_attester_trust(&self, peer_id: String, score: f32) {
        self.our_trust_scores
            .write()
            .await
            .insert(peer_id, score.clamp(0.0, 1.0));
    }

    /// Count of blocked peers
    pub async fn blocked_count(&self) -> usize {
        self.peers
            .read()
            .await
            .values()
            .filter(|r| r.trust_level == TrustLevel::Blocked)
            .count()
    }

    /// Average trust score across all known peers
    pub async fn average_trust_score(&self) -> f32 {
        let peers = self.peers.read().await;
        if peers.is_empty() {
            return 0.0;
        }
        let sum: f32 = peers.values().map(|r| r.trust_level.score()).sum();
        sum / peers.len() as f32
    }

    async fn verify_attestation_signature(&self, att: &Attestation) -> Result<(), String> {
        use ring::signature::{UnparsedPublicKey, ED25519};

        if att.signature.is_empty() {
            return Err(format!(
                "Attestation from {} has empty signature",
                att.attester_id
            ));
        }
        if att.subject_pub_key.is_empty() {
            return Err(format!(
                "Attestation from {} has empty subject public key",
                att.attester_id
            ));
        }

        let attester_pub_key = {
            let peers = self.peers.read().await;
            peers
                .get(&att.attester_id)
                .map(|r| r.current_key.clone())
                .ok_or_else(|| {
                    format!(
                        "Cannot verify attestation from unknown peer: {}",
                        att.attester_id
                    )
                })?
        };

        let payload = [
            att.subject_id.as_bytes(),
            &att.subject_pub_key,
            &att.timestamp.to_le_bytes(),
        ]
        .concat();

        let pub_key = UnparsedPublicKey::new(&ED25519, &attester_pub_key);
        pub_key.verify(&payload, &att.signature).map_err(|e| {
            format!(
                "Attestation signature verification failed for {}{}: {}",
                att.attester_id, att.subject_id, e
            )
        })
    }
}

impl Default for TrustStore {
    fn default() -> Self {
        Self::new()
    }
}

fn now_secs() -> u64 {
    std::time::SystemTime::now()
        .duration_since(std::time::UNIX_EPOCH)
        .unwrap_or_default()
        .as_secs()
}

#[cfg(test)]
mod tests {
    use super::*;

    #[tokio::test]
    async fn tofu_pin_on_first_contact() {
        let store = TrustStore::new();
        let key = vec![0x01u8; 32];
        let is_new = store.verify_or_pin("peer1", &key).await.unwrap();
        assert!(is_new);
        let record = store.get("peer1").await.unwrap();
        assert_eq!(record.trust_level, TrustLevel::FirstContact);
    }

    #[tokio::test]
    async fn second_contact_with_same_key_is_trusted() {
        let store = TrustStore::new();
        let key = vec![0x02u8; 32];
        store.verify_or_pin("peer1", &key).await.unwrap();
        let is_new = store.verify_or_pin("peer1", &key).await.unwrap();
        assert!(!is_new);
        let record = store.get("peer1").await.unwrap();
        assert_eq!(record.trust_level, TrustLevel::Trusted);
    }

    #[tokio::test]
    async fn key_change_returns_error() {
        let store = TrustStore::new();
        let key1 = vec![0x03u8; 32];
        let key2 = vec![0x04u8; 32];
        store.verify_or_pin("peer1", &key1).await.unwrap();
        let result = store.verify_or_pin("peer1", &key2).await;
        assert!(result.is_err());
        let record = store.get("peer1").await.unwrap();
        assert_eq!(record.trust_level, TrustLevel::KeyChanged);
    }

    #[tokio::test]
    async fn block_sets_level() {
        let store = TrustStore::new();
        let key = vec![0x05u8; 32];
        store.verify_or_pin("peer1", &key).await.unwrap();
        store.block("peer1").await;
        let record = store.get("peer1").await.unwrap();
        assert_eq!(record.trust_level, TrustLevel::Blocked);
        assert!(!record.trust_level.is_usable());
    }
}