fedimint_core/net/
guardian_metadata.rs1use bitcoin::hashes::{Hash, sha256};
2use bitcoin::secp256k1::Message;
3use fedimint_core::encoding::{Decodable, DecodeContext as _, DecodeError, Encodable};
4use serde::{Deserialize, Serialize};
5
6use crate::util::SafeUrl;
7
8const GUARDIAN_METADATA_MESSAGE_TAG: &[u8] = b"fedimint-guardian-metadata";
9const MAX_FUTURE_TIMESTAMP_SECS: u64 = 3600;
11
12#[derive(Debug, Serialize, Deserialize, Clone, Eq, Hash, PartialEq)]
13pub struct GuardianMetadata {
14 pub api_urls: Vec<SafeUrl>,
15 pub pkarr_id_z32: String,
17 pub timestamp_secs: u64,
18 #[serde(default, skip_serializing_if = "Option::is_none")]
20 pub iroh_next_endpoint: Option<String>,
21}
22
23#[derive(Debug, Clone, Eq, Hash, PartialEq)]
24pub struct SignedGuardianMetadata {
25 pub bytes: Vec<u8>,
27 pub value: GuardianMetadata,
29 pub signature: secp256k1::schnorr::Signature,
30}
31
32#[derive(Debug, Serialize, Deserialize, Clone, Eq, Hash, PartialEq, Encodable, Decodable)]
33pub struct SignedGuardianMetadataSubmission {
34 #[serde(flatten)]
35 pub signed_guardian_metadata: SignedGuardianMetadata,
36 pub peer_id: crate::PeerId,
37}
38
39impl Serialize for SignedGuardianMetadata {
46 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
47 where
48 S: serde::Serializer,
49 {
50 use serde::ser::SerializeStruct;
51 let mut state = serializer.serialize_struct("SignedGuardianMetadata", 2)?;
52
53 let content = String::from_utf8(self.bytes.clone())
55 .map_err(|e| serde::ser::Error::custom(format!("Invalid UTF-8 in bytes: {e}")))?;
56 state.serialize_field("content", &content)?;
57
58 state.serialize_field("signature", &hex::encode(self.signature.as_ref()))?;
60 state.end()
61 }
62}
63
64impl<'de> Deserialize<'de> for SignedGuardianMetadata {
65 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
66 where
67 D: serde::Deserializer<'de>,
68 {
69 use serde::de::Error;
70
71 #[derive(Deserialize)]
72 struct SignedGuardianMetadataHelper {
73 content: String,
74 signature: String,
75 }
76
77 let helper = SignedGuardianMetadataHelper::deserialize(deserializer)?;
78
79 let bytes = helper.content.into_bytes();
80 let value: GuardianMetadata = serde_json::from_slice(&bytes).map_err(D::Error::custom)?;
81 let signature_bytes = hex::decode(&helper.signature).map_err(D::Error::custom)?;
82 let signature = secp256k1::schnorr::Signature::from_slice(&signature_bytes)
83 .map_err(D::Error::custom)?;
84
85 Ok(Self {
86 bytes,
87 value,
88 signature,
89 })
90 }
91}
92
93impl Encodable for SignedGuardianMetadata {
95 fn consensus_encode<W: std::io::Write>(&self, writer: &mut W) -> Result<(), std::io::Error> {
96 self.bytes.consensus_encode(writer)?;
98 self.signature.consensus_encode(writer)?;
99 Ok(())
100 }
101}
102
103impl Decodable for SignedGuardianMetadata {
104 fn consensus_decode_partial_from_finite_reader<R: std::io::Read>(
105 reader: &mut R,
106 modules: &fedimint_core::module::registry::ModuleDecoderRegistry,
107 ) -> Result<Self, DecodeError> {
108 let bytes = Vec::<u8>::consensus_decode_partial_from_finite_reader(reader, modules)?;
109 let value: GuardianMetadata = serde_json::from_slice(&bytes)
110 .map_err(DecodeError::from_err)
111 .context("Invalid JSON")?;
112 let signature = secp256k1::schnorr::Signature::consensus_decode_partial_from_finite_reader(
113 reader, modules,
114 )?;
115
116 Ok(Self {
117 bytes,
118 value,
119 signature,
120 })
121 }
122}
123
124fn compute_tagged_hash(json_bytes: &[u8]) -> sha256::Hash {
125 use bitcoin::hashes::HashEngine;
126 let mut engine = sha256::HashEngine::default();
127 engine.input(GUARDIAN_METADATA_MESSAGE_TAG);
128 engine.input(json_bytes);
129 sha256::Hash::from_engine(engine)
130}
131
132#[derive(Debug, thiserror::Error)]
133pub enum VerificationError {
134 #[error("Invalid signature")]
135 InvalidSignature,
136 #[error("Timestamp {timestamp_secs} is too far in the future (max allowed: {max_allowed})")]
137 TimestampTooFarInFuture {
138 timestamp_secs: u64,
139 max_allowed: u64,
140 },
141}
142
143impl GuardianMetadata {
144 pub fn new(api_urls: Vec<SafeUrl>, pkarr_id_z32: String, timestamp_secs: u64) -> Self {
145 Self {
146 api_urls,
147 pkarr_id_z32,
148 timestamp_secs,
149 iroh_next_endpoint: None,
150 }
151 }
152
153 pub fn with_iroh_next_endpoint(mut self, endpoint: String) -> Self {
155 self.iroh_next_endpoint = Some(endpoint);
156 self
157 }
158
159 pub fn sign<C: secp256k1::Signing>(
160 &self,
161 ctx: &secp256k1::Secp256k1<C>,
162 key: &secp256k1::Keypair,
163 ) -> SignedGuardianMetadata {
164 let bytes = serde_json::to_vec(self).expect("JSON serialization should not fail");
166 let tagged_hash = compute_tagged_hash(&bytes);
167
168 let msg = Message::from_digest(*tagged_hash.as_ref());
169 let signature = ctx.sign_schnorr(&msg, key);
170
171 SignedGuardianMetadata {
172 bytes,
173 value: self.clone(),
174 signature,
175 }
176 }
177}
178
179impl SignedGuardianMetadata {
180 pub fn guardian_metadata(&self) -> &GuardianMetadata {
182 &self.value
183 }
184
185 pub fn tagged_hash(&self) -> sha256::Hash {
187 compute_tagged_hash(&self.bytes)
188 }
189
190 pub fn verify<C: secp256k1::Verification>(
195 &self,
196 ctx: &secp256k1::Secp256k1<C>,
197 pk: &secp256k1::PublicKey,
198 now: std::time::Duration,
199 ) -> Result<(), VerificationError> {
200 let msg = Message::from_digest(*self.tagged_hash().as_ref());
202 ctx.verify_schnorr(&self.signature, &msg, &pk.x_only_public_key().0)
203 .map_err(|_| VerificationError::InvalidSignature)?;
204
205 let current_secs = now.as_secs();
207 let max_allowed_timestamp = current_secs.saturating_add(MAX_FUTURE_TIMESTAMP_SECS);
208
209 if max_allowed_timestamp < self.value.timestamp_secs {
210 return Err(VerificationError::TimestampTooFarInFuture {
211 timestamp_secs: self.value.timestamp_secs,
212 max_allowed: max_allowed_timestamp,
213 });
214 }
215
216 Ok(())
217 }
218}
219
220#[cfg(test)]
221mod tests;