Skip to main content

actpub_activitystreams/
multikey.rs

1//! [FEP-521a Multikey][fep521a] verification-method type.
2//!
3//! FEP-521a defines a uniform way to publish per-actor verification
4//! keys via the W3C [Controlled Identifiers][cid] vocabulary. Each
5//! `assertionMethod` (or `authentication`) entry is either a bare URL
6//! reference to a remote key document or an inlined [`Multikey`] block
7//! carrying a `multibase`-encoded public key.
8//!
9//! The `publicKeyMultibase` payload uses the `multicodec` envelope to
10//! describe the key type:
11//!
12//! | Codec prefix (varint) | Algorithm |
13//! |-----------------------|-----------|
14//! | `0xed01`              | Ed25519   |
15//! | `0x1200`              | secp256r1 (P-256) |
16//! | `0x1205`              | RSA       |
17//!
18//! This crate models the wire form only. Encoding/decoding the
19//! multibase payload back to raw key bytes is handled by
20//! `actpub-httpsig::Multikey`, keeping cryptographic primitives in the
21//! crypto crate.
22//!
23//! [fep521a]: https://codeberg.org/fediverse/fep/src/branch/main/fep/521a/fep-521a.md
24//! [cid]: https://www.w3.org/TR/cid-1.0/
25
26use serde::{Deserialize, Serialize};
27use url::Url;
28
29use crate::value::UrlOr;
30
31/// FEP-521a `Multikey` block.
32#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
33#[serde(rename_all = "camelCase")]
34pub struct Multikey {
35    /// Globally unique identifier for this key, typically the actor URL
36    /// suffixed with a `#key-N` fragment.
37    pub id: Url,
38
39    /// Type discriminator. MUST be `"Multikey"` per FEP-521a §3.
40    #[serde(rename = "type")]
41    pub kind: String,
42
43    /// Actor that owns and rotates this key.
44    pub controller: Url,
45
46    /// Multibase-encoded multicodec public key payload (e.g.
47    /// `z6Mk…` for Ed25519, `zDn…` for P-256).
48    pub public_key_multibase: String,
49}
50
51impl Multikey {
52    /// The fixed type discriminator value mandated by FEP-521a.
53    pub const TYPE: &'static str = "Multikey";
54
55    /// Builds a [`Multikey`] with the canonical `"Multikey"` type.
56    #[must_use]
57    pub fn new(id: Url, controller: Url, public_key_multibase: impl Into<String>) -> Self {
58        Self {
59            id,
60            kind: Self::TYPE.to_owned(),
61            controller,
62            public_key_multibase: public_key_multibase.into(),
63        }
64    }
65}
66
67/// A reference to a verification method, either as a bare URL or an
68/// inlined [`Multikey`] document.
69///
70/// This is the value type shared by `ActivityPub` `assertionMethod` and
71/// `authentication` arrays per FEP-521a §4 and the W3C Controlled
72/// Identifiers spec.
73pub type AssertionMethod = UrlOr<Multikey>;
74
75#[cfg(test)]
76mod tests {
77    use pretty_assertions::assert_eq;
78    use serde_json::json;
79
80    use super::*;
81
82    #[test]
83    fn inline_multikey_roundtrips() {
84        let raw = json!({
85            "id": "https://example.com/users/alice#ed25519-key",
86            "type": "Multikey",
87            "controller": "https://example.com/users/alice",
88            "publicKeyMultibase": "z6MkrJVnaZkeFzdQyMZu1cgjg7k1pZZ6pvBQ7XJPt4swbTQ2"
89        });
90        let key: Multikey = serde_json::from_value(raw.clone()).unwrap();
91        assert_eq!(key.kind, Multikey::TYPE);
92        assert!(key.public_key_multibase.starts_with('z'));
93        let back = serde_json::to_value(&key).unwrap();
94        assert_eq!(back, raw);
95    }
96
97    #[test]
98    fn assertion_method_accepts_bare_url_form() {
99        let raw = json!("https://example.com/users/alice#main-key");
100        let am: AssertionMethod = serde_json::from_value(raw.clone()).unwrap();
101        assert!(matches!(am, AssertionMethod::Url(_)));
102        let back = serde_json::to_value(&am).unwrap();
103        assert_eq!(back, raw);
104    }
105
106    #[test]
107    fn assertion_method_accepts_inlined_multikey_form() {
108        let raw = json!({
109            "id": "https://example.com/users/alice#main-key",
110            "type": "Multikey",
111            "controller": "https://example.com/users/alice",
112            "publicKeyMultibase": "z6Mk…"
113        });
114        let am: AssertionMethod = serde_json::from_value(raw.clone()).unwrap();
115        assert!(matches!(am, AssertionMethod::Object(_)));
116        let back = serde_json::to_value(&am).unwrap();
117        assert_eq!(back, raw);
118    }
119}