holographic-memory 0.6.3

Local-first search engine and agent memory: fast approximate nearest-neighbor and BM25 full-text search, encrypted stores, provable deletion, built on vector-symbolic architectures.
Documentation
// Copyright 2024-2026 WritersLogic Contributors
// SPDX-License-Identifier: Apache-2.0

//! Caller-supplied trust anchors for provenance verification.
//!
//! Signature checks alone only prove a record is *internally self-consistent*:
//! the key embedded in the record verifies the signature the record carries.
//! Authenticity requires an independent trust decision — "do I trust the party
//! that holds this key?". A [`TrustStore`] is the set of keys/issuers the caller
//! has decided to trust; verification is authentic only when the key that
//! produced a signature is present in the store.

use std::collections::{HashMap, HashSet};

use anyhow::Result;
use ed25519_dalek::VerifyingKey;

use super::did;

/// A set of trusted Ed25519 verification keys.
///
/// A record is treated as authentic only if the key that produced its
/// signature is in this set. Keys can be added directly or via their
/// `did:key` representation.
#[derive(Default, Clone)]
pub struct TrustStore {
    keys: HashSet<[u8; 32]>,
    /// Revocation bitstrings, keyed by the status list credential URL that credentials cite.
    status_lists: HashMap<String, Vec<u8>>,
    /// Whether a credential citing a status list this store does not hold fails its
    /// revocation check instead of passing unchecked.
    require_status_lists: bool,
}

/// Whether bit `index` of a revocation bitstring is set: byte `index / 8`, bit `index % 8`
/// counted from the least significant bit. Indices past the end are not revoked.
pub(crate) fn status_bit(bits: &[u8], index: u64) -> bool {
    usize::try_from(index / 8)
        .ok()
        .and_then(|byte_idx| bits.get(byte_idx))
        .is_some_and(|byte| byte & (1 << (index % 8)) != 0)
}

impl TrustStore {
    /// An empty trust store. It trusts nothing, so every record verifies as
    /// non-authentic until keys are added.
    pub fn new() -> Self {
        Self::default()
    }

    /// Trust a raw verifying key.
    pub fn trust_key(&mut self, key: &VerifyingKey) -> &mut Self {
        self.keys.insert(key.to_bytes());
        self
    }

    /// Trust the holder of a `did:key` identifier. Errors if the DID is not a
    /// valid Ed25519 `did:key`.
    pub fn trust_did(&mut self, did_key: &str) -> Result<&mut Self> {
        let pk = did::ed25519_from_did_key(did_key)?;
        self.keys.insert(pk);
        Ok(self)
    }

    /// Trust the holder of a `did:jwk` identifier (used by CAWG ICA issuers).
    /// Errors if the DID is not a valid Ed25519 `did:jwk`.
    pub fn trust_did_jwk(&mut self, did_jwk: &str) -> Result<&mut Self> {
        let pk = did::ed25519_from_did_jwk(did_jwk)?;
        self.keys.insert(pk);
        Ok(self)
    }

    /// Convenience constructor: a store trusting a single verifying key.
    pub fn trusting_key(key: &VerifyingKey) -> Self {
        let mut s = Self::new();
        s.trust_key(key);
        s
    }

    /// Convenience constructor: a store trusting a single `did:key` issuer.
    pub fn trusting_did(did_key: &str) -> Result<Self> {
        let mut s = Self::new();
        s.trust_did(did_key)?;
        Ok(s)
    }

    /// Whether the given key is trusted.
    pub fn is_trusted(&self, key: &VerifyingKey) -> bool {
        self.keys.contains(&key.to_bytes())
    }

    /// Trust `bits` as the current revocation bitstring of the status list published at
    /// `credential_url`, replacing any list trusted for that URL. Credentials citing that URL
    /// then verify as revoked when their bit is set.
    pub fn trust_status_list(&mut self, credential_url: &str, bits: Vec<u8>) -> &mut Self {
        self.status_lists.insert(credential_url.to_string(), bits);
        self
    }

    /// Whether the status list trusted for `credential_url` revokes `index`, or `None` when no
    /// list is trusted for that URL.
    pub fn is_revoked(&self, credential_url: &str, index: u64) -> Option<bool> {
        self.status_lists
            .get(credential_url)
            .map(|bits| status_bit(bits, index))
    }

    /// Makes a credential whose status list this store does not hold fail verification
    /// (`revocation_status` not passed) instead of passing with "revocation not checked".
    /// Off by default.
    pub fn require_status_lists(&mut self, required: bool) -> &mut Self {
        self.require_status_lists = required;
        self
    }

    /// Whether [`Self::require_status_lists`] is on.
    pub fn requires_status_lists(&self) -> bool {
        self.require_status_lists
    }

    /// Whether the store trusts nothing.
    pub fn is_empty(&self) -> bool {
        self.keys.is_empty()
    }

    /// Number of distinct trusted keys.
    pub fn len(&self) -> usize {
        self.keys.len()
    }
}

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

    fn key() -> SigningKey {
        SigningKey::from_bytes(&rand::random())
    }

    #[test]
    fn empty_trusts_nothing() {
        let store = TrustStore::new();
        assert!(store.is_empty());
        assert!(!store.is_trusted(&key().verifying_key()));
    }

    #[test]
    fn trusts_added_key_only() {
        let trusted = key();
        let other = key();
        let store = TrustStore::trusting_key(&trusted.verifying_key());
        assert!(store.is_trusted(&trusted.verifying_key()));
        assert!(!store.is_trusted(&other.verifying_key()));
        assert_eq!(store.len(), 1);
    }

    #[test]
    fn trust_via_did_roundtrips() {
        let k = key();
        let did = did::did_key_from_ed25519(&k.verifying_key().to_bytes());
        let store = TrustStore::trusting_did(&did).unwrap();
        assert!(store.is_trusted(&k.verifying_key()));
    }
}