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