Skip to main content

spacedb_access/
chain.rs

1//! Delegation chains — bounded re-delegation of a capability.
2//!
3//! A bearer holding a delegable capability (`delegation_depth > 0`) can issue a
4//! **sub-grant** to another bearer, narrower than the one it holds. The presented
5//! credential is then a [`CapabilityChain`]: `[root, sub₁, sub₂, …]` where each
6//! link is signed by the previous link's bearer (the delegator), and
7//! [`authorize_chain`](crate::authorize_chain) enforces that every step narrows
8//! (scope ⊆, ops ⊆, expiry ≤, depth −1) and that no link is revoked.
9//!
10//! Accountability propagates down the chain: the final bearer's authority traces
11//! back, link by link, to the owner who signed the root.
12
13use serde::{Deserialize, Serialize};
14
15use crate::capability::{Capability, SignedCapability};
16use crate::error::{AccessError, AccessResult};
17use crate::identity::{Did, Identity};
18
19/// A root capability plus a sequence of sub-grants, each signed by the previous
20/// bearer.
21#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
22pub struct CapabilityChain {
23    links: Vec<SignedCapability>,
24}
25
26impl CapabilityChain {
27    /// A chain of just the root grant (no delegation).
28    pub fn single(root: SignedCapability) -> Self {
29        Self { links: vec![root] }
30    }
31
32    /// The links, `[root, …, leaf]`.
33    pub fn links(&self) -> &[SignedCapability] {
34        &self.links
35    }
36
37    pub fn len(&self) -> usize {
38        self.links.len()
39    }
40
41    pub fn is_empty(&self) -> bool {
42        self.links.is_empty()
43    }
44
45    /// The bearer the chain ultimately authorizes (the leaf bearer).
46    pub fn bearer(&self) -> Option<&Did> {
47        self.links.last().map(|l| &l.capability.bearer)
48    }
49
50    /// Serialize the chain (postcard) for transmission.
51    pub fn encode(&self) -> AccessResult<Vec<u8>> {
52        postcard::to_allocvec(self).map_err(|e| AccessError::Canonical(e.to_string()))
53    }
54
55    /// Deserialize a chain (postcard).
56    pub fn decode(bytes: &[u8]) -> AccessResult<Self> {
57        postcard::from_bytes(bytes).map_err(|e| AccessError::Canonical(e.to_string()))
58    }
59}
60
61impl From<SignedCapability> for CapabilityChain {
62    fn from(root: SignedCapability) -> Self {
63        Self::single(root)
64    }
65}
66
67/// Extend `parent` by delegating `sub`, signed by `delegator`. The narrowing and
68/// depth constraints are enforced at authorization time
69/// ([`authorize_chain`](crate::authorize_chain)); this just signs and appends, so
70/// callers should build `sub` with `issuer = delegator.did()` and a scope/ops/
71/// expiry within the parent.
72pub fn delegate(
73    parent: &CapabilityChain,
74    sub: Capability,
75    delegator: &Identity,
76) -> AccessResult<CapabilityChain> {
77    let signed = SignedCapability::sign(sub, delegator)?;
78    let mut links = parent.links.clone();
79    links.push(signed);
80    Ok(CapabilityChain { links })
81}