1use chrono::{DateTime, Utc};
5use objects::object::{ContentHash, KeyBinding, KeyBindingRegistry, StateSignature};
6use serde::{Deserialize, Serialize};
7
8use crate::{ObjectData, ObjectId, ObjectType, ProtocolError, Result};
9
10#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
16pub struct WireKeyBindingLiveness {
17 pub revoked_at: Option<DateTime<Utc>>,
18}
19
20#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
22pub struct WireKeyBinding {
23 pub algorithm: String,
24 pub public_key: String,
25 pub identity_ref: String,
26 pub role: String,
27 pub added_by_sig: StateSignature,
28 pub valid_from: DateTime<Utc>,
29 pub delegated_from: Option<ContentHash>,
30 pub liveness: WireKeyBindingLiveness,
31}
32
33#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
35pub struct WireKeyBindingRegistry {
36 pub format_version: u8,
37 pub bindings: Vec<WireKeyBinding>,
38}
39
40impl From<&KeyBinding> for WireKeyBinding {
41 fn from(binding: &KeyBinding) -> Self {
42 Self {
43 algorithm: binding.algorithm.clone(),
44 public_key: binding.public_key.clone(),
45 identity_ref: binding.identity_ref.clone(),
46 role: binding.role.clone(),
47 added_by_sig: binding.added_by_sig.clone(),
48 valid_from: binding.valid_from,
49 delegated_from: binding.delegated_from,
50 liveness: WireKeyBindingLiveness {
51 revoked_at: binding.revoked_at,
52 },
53 }
54 }
55}
56
57impl From<WireKeyBinding> for KeyBinding {
58 fn from(binding: WireKeyBinding) -> Self {
59 Self {
60 algorithm: binding.algorithm,
61 public_key: binding.public_key,
62 identity_ref: binding.identity_ref,
63 role: binding.role,
64 added_by_sig: binding.added_by_sig,
65 valid_from: binding.valid_from,
66 revoked_at: binding.liveness.revoked_at,
67 delegated_from: binding.delegated_from,
68 }
69 }
70}
71
72impl From<&KeyBindingRegistry> for WireKeyBindingRegistry {
73 fn from(registry: &KeyBindingRegistry) -> Self {
74 Self {
75 format_version: registry.format_version,
76 bindings: registry.bindings.iter().map(WireKeyBinding::from).collect(),
77 }
78 }
79}
80
81impl From<WireKeyBindingRegistry> for KeyBindingRegistry {
82 fn from(registry: WireKeyBindingRegistry) -> Self {
83 Self {
84 format_version: registry.format_version,
85 bindings: registry
86 .bindings
87 .into_iter()
88 .map(KeyBinding::from)
89 .collect(),
90 }
91 }
92}
93
94pub fn encode_key_binding_registry(registry: &KeyBindingRegistry) -> Result<ObjectData> {
96 registry
97 .validate()
98 .map_err(|error| ProtocolError::InvalidState(error.to_string()))?;
99 let id = registry
100 .content_hash()
101 .map_err(|error| ProtocolError::InvalidState(error.to_string()))?;
102 let wire = WireKeyBindingRegistry::from(registry);
103 Ok(ObjectData {
104 id: ObjectId::Hash(id),
105 obj_type: ObjectType::KeyBinding,
106 data: rmp_serde::to_vec_named(&wire)?,
107 is_delta: false,
108 })
109}
110
111pub fn decode_key_binding_registry(data: &ObjectData) -> Result<KeyBindingRegistry> {
113 let (ObjectId::Hash(expected), ObjectType::KeyBinding) = (&data.id, data.obj_type) else {
114 return Err(ProtocolError::InvalidState(
115 "object id/type mismatch for key-binding registry".to_string(),
116 ));
117 };
118 if data.is_delta {
119 return Err(ProtocolError::InvalidState(
120 "key-binding registry objects cannot be delta encoded".to_string(),
121 ));
122 }
123 let wire: WireKeyBindingRegistry = rmp_serde::from_slice(&data.data)?;
124 let registry = KeyBindingRegistry::from(wire);
125 registry
126 .validate()
127 .map_err(|error| ProtocolError::InvalidState(error.to_string()))?;
128 let actual = registry
129 .content_hash()
130 .map_err(|error| ProtocolError::InvalidState(error.to_string()))?;
131 if actual != *expected {
132 return Err(ProtocolError::InvalidState(format!(
133 "key-binding registry hash mismatch: expected {}, computed {}",
134 expected.to_hex(),
135 actual.to_hex()
136 )));
137 }
138 Ok(registry)
139}
140
141#[cfg(test)]
142mod tests {
143 use chrono::{TimeZone, Utc};
144 use objects::object::{ContentHash, KeyBinding, KeyBindingRegistry, StateSignature};
145
146 use super::{WireKeyBinding, decode_key_binding_registry, encode_key_binding_registry};
147 use crate::{ObjectData, ObjectType};
148
149 #[test]
150 fn key_binding_registry_roundtrips_with_liveness_overlay_losslessly() {
151 let binding = KeyBinding {
152 algorithm: "ed25519".to_string(),
153 public_key: "11".repeat(32),
154 identity_ref: "user:01HZZZZZZZZZZZZZZZZZZZZZZZ".to_string(),
155 role: "author".to_string(),
156 added_by_sig: StateSignature {
157 algorithm: "ed25519".to_string(),
158 public_key: "22".repeat(32),
159 signature: "33".repeat(64),
160 },
161 valid_from: Utc.timestamp_opt(1_725_000_000, 123_456_789).unwrap(),
162 revoked_at: Some(Utc.timestamp_opt(1_735_000_000, 987_654_321).unwrap()),
163 delegated_from: Some(ContentHash::from_bytes([0x44; 32])),
164 };
165 let wire_binding = WireKeyBinding::from(&binding);
166 assert_eq!(wire_binding.liveness.revoked_at, binding.revoked_at);
167 assert_eq!(KeyBinding::from(wire_binding), binding);
168 let registry = KeyBindingRegistry::new(vec![binding]);
169
170 let wire = encode_key_binding_registry(®istry).expect("encode wire registry");
171 assert_eq!(wire.obj_type, ObjectType::KeyBinding);
172 assert_eq!(ObjectType::KeyBinding.wire_name(), "key_binding");
173 assert_eq!(
174 ObjectType::from_wire("key_binding").expect("parse key-binding type"),
175 ObjectType::KeyBinding
176 );
177 let encoded_frame = rmp_serde::to_vec_named(&wire).expect("encode object frame");
178 let transferred: ObjectData =
179 rmp_serde::from_slice(&encoded_frame).expect("decode object frame");
180 let decoded = decode_key_binding_registry(&transferred).expect("decode wire registry");
181
182 assert_eq!(decoded, registry);
183 assert_eq!(
184 decoded.content_hash().unwrap(),
185 registry.content_hash().unwrap()
186 );
187 }
188
189 #[test]
190 fn key_binding_registry_rejects_a_mismatched_content_address() {
191 let registry = KeyBindingRegistry::empty();
192 let mut wire = encode_key_binding_registry(®istry).expect("encode wire registry");
193 wire.id = crate::ObjectId::Hash(ContentHash::from_bytes([0x55; 32]));
194
195 let error = decode_key_binding_registry(&wire).expect_err("hash mismatch must fail closed");
196 assert!(error.to_string().contains("hash mismatch"));
197 }
198}