fedimint_core/
config.rs

1use std::collections::{BTreeMap, BTreeSet};
2use std::fmt::{Debug, Display};
3use std::hash::Hash;
4use std::ops::Mul;
5use std::path::Path;
6use std::str::FromStr;
7
8use anyhow::{bail, format_err, Context};
9use bitcoin::hashes::sha256::{Hash as Sha256, HashEngine};
10use bitcoin::hashes::{hex, sha256, Hash as BitcoinHash};
11use bls12_381::Scalar;
12use fedimint_core::core::{ModuleInstanceId, ModuleKind};
13use fedimint_core::encoding::{DynRawFallback, Encodable};
14use fedimint_core::module::registry::ModuleRegistry;
15use fedimint_core::util::SafeUrl;
16use fedimint_core::{format_hex, ModuleDecoderRegistry};
17use fedimint_logging::LOG_CORE;
18use hex::FromHex;
19use secp256k1::PublicKey;
20use serde::de::DeserializeOwned;
21use serde::ser::SerializeMap;
22use serde::{Deserialize, Deserializer, Serialize, Serializer};
23use serde_json::json;
24use threshold_crypto::group::{Curve, Group, GroupEncoding};
25use threshold_crypto::{G1Projective, G2Projective};
26use tracing::warn;
27
28use crate::core::DynClientConfig;
29use crate::encoding::Decodable;
30use crate::module::{
31    CoreConsensusVersion, DynCommonModuleInit, IDynCommonModuleInit, ModuleConsensusVersion,
32};
33use crate::{bls12_381_serde, maybe_add_send_sync, PeerId};
34
35// TODO: make configurable
36/// This limits the RAM consumption of a AlephBFT Unit to roughly 50kB
37pub const ALEPH_BFT_UNIT_BYTE_LIMIT: usize = 50_000;
38
39/// [`serde_json::Value`] that must contain `kind: String` field
40///
41/// TODO: enforce at ser/deserialization
42/// TODO: make inside prive and enforce `kind` on construction, to
43/// other functions non-falliable
44#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
45pub struct JsonWithKind {
46    kind: ModuleKind,
47    #[serde(flatten)]
48    value: serde_json::Value,
49}
50
51impl JsonWithKind {
52    pub fn new(kind: ModuleKind, value: serde_json::Value) -> Self {
53        Self { kind, value }
54    }
55
56    /// Workaround for a serde `flatten` quirk
57    ///
58    /// We serialize config with no fields as: eg. `{ kind: "ln" }`.
59    ///
60    /// When `kind` gets removed and `value` is parsed, it will
61    /// parse as `Value::Object` that is empty.
62    ///
63    /// However empty module structs, like `struct FooConfigLocal;` (unit
64    /// struct), will fail to deserialize with this value, as they expect
65    /// `Value::Null`.
66    ///
67    /// We can turn manually empty object into null, and that's what
68    /// we do in this function. This fixes the deserialization into
69    /// unit type, but in turn breaks deserialization into `struct Foo{}`,
70    /// which is arguably much less common, but valid.
71    ///
72    /// TODO: In the future, we should have a typed and erased versions of
73    /// module construction traits, and then we can try with and
74    /// without the workaround to have both cases working.
75    /// See <https://github.com/fedimint/fedimint/issues/1303>
76    pub fn with_fixed_empty_value(self) -> Self {
77        if let serde_json::Value::Object(ref o) = self.value {
78            if o.is_empty() {
79                return Self {
80                    kind: self.kind,
81                    value: serde_json::Value::Null,
82                };
83            }
84        }
85
86        self
87    }
88
89    pub fn value(&self) -> &serde_json::Value {
90        &self.value
91    }
92
93    pub fn kind(&self) -> &ModuleKind {
94        &self.kind
95    }
96
97    pub fn is_kind(&self, kind: &ModuleKind) -> bool {
98        &self.kind == kind
99    }
100}
101
102#[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize, Encodable, Decodable)]
103pub struct PeerUrl {
104    /// The peer's public URL (e.g. `wss://fedimint-server-1:5000`)
105    pub url: SafeUrl,
106    /// The peer's name
107    pub name: String,
108}
109
110/// Total client config v0 (<0.4.0). Does not contain broadcast public keys.
111///
112/// This includes global settings and client-side module configs.
113#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize, Encodable, Decodable)]
114pub struct ClientConfigV0 {
115    #[serde(flatten)]
116    pub global: GlobalClientConfigV0,
117    #[serde(deserialize_with = "de_int_key")]
118    pub modules: BTreeMap<ModuleInstanceId, ClientModuleConfig>,
119}
120
121/// Total client config
122///
123/// This includes global settings and client-side module configs.
124#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize, Encodable, Decodable)]
125pub struct ClientConfig {
126    #[serde(flatten)]
127    pub global: GlobalClientConfig,
128    #[serde(deserialize_with = "de_int_key")]
129    pub modules: BTreeMap<ModuleInstanceId, ClientModuleConfig>,
130}
131
132// FIXME: workaround for https://github.com/serde-rs/json/issues/989
133fn de_int_key<'de, D, K, V>(deserializer: D) -> Result<BTreeMap<K, V>, D::Error>
134where
135    D: Deserializer<'de>,
136    K: Eq + Ord + FromStr,
137    K::Err: Display,
138    V: Deserialize<'de>,
139{
140    let string_map = <BTreeMap<String, V>>::deserialize(deserializer)?;
141    let map = string_map
142        .into_iter()
143        .map(|(key_str, value)| {
144            let key = K::from_str(&key_str).map_err(serde::de::Error::custom)?;
145            Ok((key, value))
146        })
147        .collect::<Result<BTreeMap<_, _>, _>>()?;
148    Ok(map)
149}
150
151fn optional_de_int_key<'de, D, K, V>(deserializer: D) -> Result<Option<BTreeMap<K, V>>, D::Error>
152where
153    D: Deserializer<'de>,
154    K: Eq + Ord + FromStr,
155    K::Err: Display,
156    V: Deserialize<'de>,
157{
158    let Some(string_map) = <Option<BTreeMap<String, V>>>::deserialize(deserializer)? else {
159        return Ok(None);
160    };
161
162    let map = string_map
163        .into_iter()
164        .map(|(key_str, value)| {
165            let key = K::from_str(&key_str).map_err(serde::de::Error::custom)?;
166            Ok((key, value))
167        })
168        .collect::<Result<BTreeMap<_, _>, _>>()?;
169
170    Ok(Some(map))
171}
172
173/// Client config that cannot be cryptographically verified but is easier to
174/// parse by external tools
175#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
176pub struct JsonClientConfig {
177    pub global: GlobalClientConfig,
178    pub modules: BTreeMap<ModuleInstanceId, JsonWithKind>,
179}
180
181/// Federation-wide client config
182#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize, Encodable, Decodable)]
183pub struct GlobalClientConfigV0 {
184    /// API endpoints for each federation member
185    #[serde(deserialize_with = "de_int_key")]
186    pub api_endpoints: BTreeMap<PeerId, PeerUrl>,
187    /// Core consensus version
188    pub consensus_version: CoreConsensusVersion,
189    // TODO: make it a String -> serde_json::Value map?
190    /// Additional config the federation wants to transmit to the clients
191    pub meta: BTreeMap<String, String>,
192}
193
194/// Federation-wide client config
195#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize, Encodable, Decodable)]
196pub struct GlobalClientConfig {
197    /// API endpoints for each federation member
198    #[serde(deserialize_with = "de_int_key")]
199    pub api_endpoints: BTreeMap<PeerId, PeerUrl>,
200    /// Signing session keys for each federation member
201    /// Optional for 0.3.x backwards compatibility
202    #[serde(default, deserialize_with = "optional_de_int_key")]
203    pub broadcast_public_keys: Option<BTreeMap<PeerId, PublicKey>>,
204    /// Core consensus version
205    pub consensus_version: CoreConsensusVersion,
206    // TODO: make it a String -> serde_json::Value map?
207    /// Additional config the federation wants to transmit to the clients
208    pub meta: BTreeMap<String, String>,
209}
210
211impl GlobalClientConfig {
212    /// 0.4.0 and later uses a hash of broadcast public keys to calculate the
213    /// federation id. 0.3.x and earlier use a hash of api endpoints
214    pub fn calculate_federation_id(&self) -> FederationId {
215        FederationId(self.api_endpoints.consensus_hash())
216    }
217
218    /// Federation name from config metadata (if set)
219    pub fn federation_name(&self) -> Option<&str> {
220        self.meta.get(META_FEDERATION_NAME_KEY).map(|x| &**x)
221    }
222}
223
224impl ClientConfig {
225    /// See [`DynRawFallback::redecode_raw`].
226    pub fn redecode_raw(
227        self,
228        modules: &ModuleDecoderRegistry,
229    ) -> Result<Self, crate::encoding::DecodeError> {
230        Ok(Self {
231            modules: self
232                .modules
233                .into_iter()
234                .map(|(k, v)| {
235                    // Assuming this isn't running in any hot path it's better to have the debug
236                    // info than saving one allocation
237                    let kind = v.kind.clone();
238                    v.redecode_raw(modules)
239                        .context(format!("redecode_raw: instance: {k}, kind: {kind}"))
240                        .map(|v| (k, v))
241                })
242                .collect::<Result<_, _>>()?,
243            ..self
244        })
245    }
246
247    pub fn calculate_federation_id(&self) -> FederationId {
248        self.global.calculate_federation_id()
249    }
250
251    /// Get the value of a given meta field
252    pub fn meta<V: serde::de::DeserializeOwned + 'static>(
253        &self,
254        key: &str,
255    ) -> Result<Option<V>, anyhow::Error> {
256        let Some(str_value) = self.global.meta.get(key) else {
257            return Ok(None);
258        };
259        let res = serde_json::from_str(str_value)
260            .map(Some)
261            .context(format!("Decoding meta field '{key}' failed"));
262
263        // In the past we encoded some string fields as "just a string" without quotes,
264        // this code ensures that old meta values still parse since config is hard to
265        // change
266        if res.is_err() && std::any::TypeId::of::<V>() == std::any::TypeId::of::<String>() {
267            let string_ret = Box::new(str_value.clone());
268            let ret = unsafe {
269                // We can transmute a String to V because we know that V==String
270                std::mem::transmute::<Box<String>, Box<V>>(string_ret)
271            };
272            Ok(Some(*ret))
273        } else {
274            res
275        }
276    }
277
278    /// Converts a consensus-encoded client config struct to a client config
279    /// struct that when encoded as JSON shows the fields of module configs
280    /// instead of a consensus-encoded hex string.
281    ///
282    /// In case of unknown module the config value is a hex string.
283    pub fn to_json(&self) -> JsonClientConfig {
284        JsonClientConfig {
285            global: self.global.clone(),
286            modules: self
287                .modules
288                .iter()
289                .map(|(&module_instance_id, module_config)| {
290                    let module_config_json = JsonWithKind {
291                        kind: module_config.kind.clone(),
292                        value: module_config.config
293                            .clone()
294                            .decoded()
295                            .and_then(|dyn_cfg| dyn_cfg.to_json())
296                            .unwrap_or_else(|| json!({
297                            "unknown_module_hex": module_config.config.consensus_encode_to_hex()
298                        })),
299                    };
300                    (module_instance_id, module_config_json)
301                })
302                .collect(),
303        }
304    }
305}
306
307/// The federation id is a copy of the authentication threshold public key of
308/// the federation
309///
310/// Stable id so long as guardians membership does not change
311/// Unique id so long as guardians do not all collude
312#[derive(
313    Debug,
314    Copy,
315    Serialize,
316    Deserialize,
317    Clone,
318    Eq,
319    Hash,
320    PartialEq,
321    Encodable,
322    Decodable,
323    Ord,
324    PartialOrd,
325)]
326pub struct FederationId(pub sha256::Hash);
327
328#[derive(
329    Debug,
330    Copy,
331    Serialize,
332    Deserialize,
333    Clone,
334    Eq,
335    Hash,
336    PartialEq,
337    Encodable,
338    Decodable,
339    Ord,
340    PartialOrd,
341)]
342/// Prefix of the [`FederationId`], useful for UX improvements
343///
344/// Intentionally compact to save on the encoding. With 4 billion
345/// combinations real-life non-malicious collisions should never
346/// happen.
347pub struct FederationIdPrefix([u8; 4]);
348
349impl Display for FederationIdPrefix {
350    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
351        format_hex(&self.0, f)
352    }
353}
354
355impl Display for FederationId {
356    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
357        format_hex(&self.0.to_byte_array(), f)
358    }
359}
360
361impl FromStr for FederationIdPrefix {
362    type Err = anyhow::Error;
363
364    fn from_str(s: &str) -> Result<Self, Self::Err> {
365        Ok(Self(<[u8; 4]>::from_hex(s)?))
366    }
367}
368
369impl FederationIdPrefix {
370    pub fn to_bytes(&self) -> Vec<u8> {
371        self.0.to_vec()
372    }
373}
374
375/// Display as a hex encoding
376impl FederationId {
377    /// Random dummy id for testing
378    pub fn dummy() -> Self {
379        Self(sha256::Hash::from_byte_array([42; 32]))
380    }
381
382    pub(crate) fn from_byte_array(bytes: [u8; 32]) -> Self {
383        Self(sha256::Hash::from_byte_array(bytes))
384    }
385
386    pub fn to_prefix(&self) -> FederationIdPrefix {
387        FederationIdPrefix(self.0[..4].try_into().expect("can't fail"))
388    }
389
390    /// Converts a federation id to a public key to which we know but discard
391    /// the private key.
392    ///
393    /// Clients MUST never use this private key for any signing operations!
394    ///
395    /// That is ok because we only use the public key for adding a route
396    /// hint to LN invoices that tells fedimint clients that the invoice can
397    /// only be paid internally. Since no LN node with that pub key can exist
398    /// other LN senders will know that they cannot pay the invoice.
399    pub fn to_fake_ln_pub_key(
400        &self,
401        secp: &bitcoin::secp256k1::Secp256k1<bitcoin::secp256k1::All>,
402    ) -> anyhow::Result<bitcoin::secp256k1::PublicKey> {
403        let sk = bitcoin::secp256k1::SecretKey::from_slice(&self.0.to_byte_array())?;
404        Ok(bitcoin::secp256k1::PublicKey::from_secret_key(secp, &sk))
405    }
406}
407
408impl FromStr for FederationId {
409    type Err = anyhow::Error;
410
411    fn from_str(s: &str) -> Result<Self, Self::Err> {
412        Ok(Self::from_byte_array(<[u8; 32]>::from_hex(s)?))
413    }
414}
415
416impl ClientConfig {
417    /// Returns the consensus hash for a given client config
418    pub fn consensus_hash(&self) -> sha256::Hash {
419        let mut engine = HashEngine::default();
420        self.consensus_encode(&mut engine)
421            .expect("Consensus hashing should never fail");
422        sha256::Hash::from_engine(engine)
423    }
424
425    pub fn get_module<T: Decodable + 'static>(&self, id: ModuleInstanceId) -> anyhow::Result<&T> {
426        self.modules.get(&id).map_or_else(
427            || Err(format_err!("Client config for module id {id} not found")),
428            |client_cfg| client_cfg.cast(),
429        )
430    }
431
432    // TODO: rename this and one above
433    pub fn get_module_cfg(&self, id: ModuleInstanceId) -> anyhow::Result<ClientModuleConfig> {
434        self.modules.get(&id).map_or_else(
435            || Err(format_err!("Client config for module id {id} not found")),
436            |client_cfg| Ok(client_cfg.clone()),
437        )
438    }
439
440    /// (soft-deprecated): Get the first instance of a module of a given kind in
441    /// defined in config
442    ///
443    /// Since module ids are numerical and for time being we only support 1:1
444    /// mint, wallet, ln module code in the client, this is useful, but
445    /// please write any new code that avoids assumptions about available
446    /// modules.
447    pub fn get_first_module_by_kind<T: Decodable + 'static>(
448        &self,
449        kind: impl Into<ModuleKind>,
450    ) -> anyhow::Result<(ModuleInstanceId, &T)> {
451        let kind: ModuleKind = kind.into();
452        let Some((id, module_cfg)) = self.modules.iter().find(|(_, v)| v.is_kind(&kind)) else {
453            anyhow::bail!("Module kind {kind} not found")
454        };
455        Ok((*id, module_cfg.cast()?))
456    }
457
458    // TODO: rename this and above
459    pub fn get_first_module_by_kind_cfg(
460        &self,
461        kind: impl Into<ModuleKind>,
462    ) -> anyhow::Result<(ModuleInstanceId, ClientModuleConfig)> {
463        let kind: ModuleKind = kind.into();
464        self.modules
465            .iter()
466            .find(|(_, v)| v.is_kind(&kind))
467            .map(|(id, v)| (*id, v.clone()))
468            .ok_or_else(|| anyhow::format_err!("Module kind {kind} not found"))
469    }
470}
471
472#[derive(Clone, Debug)]
473pub struct ModuleInitRegistry<M>(BTreeMap<ModuleKind, M>);
474
475impl<M> ModuleInitRegistry<M> {
476    pub fn iter(&self) -> impl Iterator<Item = (&ModuleKind, &M)> {
477        self.0.iter()
478    }
479}
480
481impl<M> Default for ModuleInitRegistry<M> {
482    fn default() -> Self {
483        Self(BTreeMap::new())
484    }
485}
486
487/// Type erased `ModuleInitParams` used to generate the `ServerModuleConfig`
488/// during config gen
489#[derive(Debug, Clone, Default, Eq, PartialEq, Serialize, Deserialize)]
490pub struct ConfigGenModuleParams {
491    pub local: serde_json::Value,
492    pub consensus: serde_json::Value,
493}
494
495impl ConfigGenModuleParams {
496    pub fn new(local: serde_json::Value, consensus: serde_json::Value) -> Self {
497        Self { local, consensus }
498    }
499
500    /// Converts the JSON into typed version, errors unless both `local` and
501    /// `consensus` values are defined
502    pub fn to_typed<P: ModuleInitParams>(&self) -> anyhow::Result<P> {
503        Ok(P::from_parts(
504            Self::parse("local", self.local.clone())?,
505            Self::parse("consensus", self.consensus.clone())?,
506        ))
507    }
508
509    fn parse<P: DeserializeOwned>(name: &str, json: serde_json::Value) -> anyhow::Result<P> {
510        serde_json::from_value(json).with_context(|| format!("Schema mismatch for {name} argument"))
511    }
512
513    pub fn from_typed<P: ModuleInitParams>(p: P) -> anyhow::Result<Self> {
514        let (local, consensus) = p.to_parts();
515        Ok(Self {
516            local: serde_json::to_value(local)?,
517            consensus: serde_json::to_value(consensus)?,
518        })
519    }
520}
521
522pub type CommonModuleInitRegistry = ModuleInitRegistry<DynCommonModuleInit>;
523
524/// Registry that contains the config gen params for all modules
525pub type ServerModuleConfigGenParamsRegistry = ModuleRegistry<ConfigGenModuleParams>;
526
527impl Eq for ServerModuleConfigGenParamsRegistry {}
528
529impl PartialEq for ServerModuleConfigGenParamsRegistry {
530    fn eq(&self, other: &Self) -> bool {
531        self.iter_modules().eq(other.iter_modules())
532    }
533}
534
535impl Serialize for ServerModuleConfigGenParamsRegistry {
536    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
537        let modules: Vec<_> = self.iter_modules().collect();
538        let mut serializer = serializer.serialize_map(Some(modules.len()))?;
539        for (id, kind, params) in modules {
540            serializer.serialize_key(&id)?;
541            serializer.serialize_value(&(kind.clone(), params.clone()))?;
542        }
543        serializer.end()
544    }
545}
546
547impl<'de> Deserialize<'de> for ServerModuleConfigGenParamsRegistry {
548    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
549    where
550        D: Deserializer<'de>,
551    {
552        let json: BTreeMap<ModuleInstanceId, (ModuleKind, ConfigGenModuleParams)> =
553            Deserialize::deserialize(deserializer)?;
554        let mut params = BTreeMap::new();
555
556        for (id, (kind, module)) in json {
557            params.insert(id, (kind, module));
558        }
559        Ok(Self::from(params))
560    }
561}
562
563impl<M> From<Vec<M>> for ModuleInitRegistry<M>
564where
565    M: AsRef<dyn IDynCommonModuleInit + Send + Sync + 'static>,
566{
567    fn from(value: Vec<M>) -> Self {
568        Self(
569            value
570                .into_iter()
571                .map(|i| (i.as_ref().module_kind(), i))
572                .collect::<BTreeMap<_, _>>(),
573        )
574    }
575}
576
577impl<M> FromIterator<M> for ModuleInitRegistry<M>
578where
579    M: AsRef<maybe_add_send_sync!(dyn IDynCommonModuleInit + 'static)>,
580{
581    fn from_iter<T: IntoIterator<Item = M>>(iter: T) -> Self {
582        Self(
583            iter.into_iter()
584                .map(|i| (i.as_ref().module_kind(), i))
585                .collect::<BTreeMap<_, _>>(),
586        )
587    }
588}
589
590impl<M> ModuleInitRegistry<M> {
591    pub fn new() -> Self {
592        Self::default()
593    }
594
595    pub fn attach<T>(&mut self, gen: T)
596    where
597        T: Into<M> + 'static + Send + Sync,
598        M: AsRef<dyn IDynCommonModuleInit + 'static + Send + Sync>,
599    {
600        let gen: M = gen.into();
601        let kind = gen.as_ref().module_kind();
602        assert!(
603            self.0.insert(kind.clone(), gen).is_none(),
604            "Can't insert module of same kind twice: {kind}"
605        );
606    }
607
608    pub fn kinds(&self) -> BTreeSet<ModuleKind> {
609        self.0.keys().cloned().collect()
610    }
611
612    pub fn get(&self, k: &ModuleKind) -> Option<&M> {
613        self.0.get(k)
614    }
615}
616
617impl ModuleRegistry<ConfigGenModuleParams> {
618    pub fn attach_config_gen_params_by_id<T: ModuleInitParams>(
619        &mut self,
620        id: ModuleInstanceId,
621        kind: ModuleKind,
622        gen: T,
623    ) -> &mut Self {
624        let params = ConfigGenModuleParams::from_typed(gen)
625            .unwrap_or_else(|err| panic!("Invalid config gen params for {kind}: {err}"));
626        self.register_module(id, kind, params);
627        self
628    }
629
630    pub fn attach_config_gen_params<T: ModuleInitParams>(
631        &mut self,
632        kind: ModuleKind,
633        gen: T,
634    ) -> &mut Self {
635        let params = ConfigGenModuleParams::from_typed(gen)
636            .unwrap_or_else(|err| panic!("Invalid config gen params for {kind}: {err}"));
637        self.append_module(kind, params);
638        self
639    }
640}
641
642impl<M> ModuleInitRegistry<M>
643where
644    M: AsRef<dyn IDynCommonModuleInit + Send + Sync + 'static>,
645{
646    #[deprecated(
647        note = "You probably want `available_decoders` to support missing module kinds. If you really want a strict behavior, use `decoders_strict`"
648    )]
649    pub fn decoders<'a>(
650        &self,
651        modules: impl Iterator<Item = (ModuleInstanceId, &'a ModuleKind)>,
652    ) -> anyhow::Result<ModuleDecoderRegistry> {
653        self.decoders_strict(modules)
654    }
655
656    /// Get decoders for `modules` and fail if any is unsupported
657    pub fn decoders_strict<'a>(
658        &self,
659        modules: impl Iterator<Item = (ModuleInstanceId, &'a ModuleKind)>,
660    ) -> anyhow::Result<ModuleDecoderRegistry> {
661        let mut decoders = BTreeMap::new();
662        for (id, kind) in modules {
663            let Some(init) = self.0.get(kind) else {
664                anyhow::bail!(
665                    "Detected configuration for unsupported module id: {id}, kind: {kind}"
666                )
667            };
668
669            decoders.insert(id, (kind.clone(), init.as_ref().decoder()));
670        }
671        Ok(ModuleDecoderRegistry::from(decoders))
672    }
673
674    /// Get decoders for `modules` and skip unsupported ones
675    pub fn available_decoders<'a>(
676        &self,
677        modules: impl Iterator<Item = (ModuleInstanceId, &'a ModuleKind)>,
678    ) -> anyhow::Result<ModuleDecoderRegistry> {
679        let mut decoders = BTreeMap::new();
680        for (id, kind) in modules {
681            let Some(init) = self.0.get(kind) else {
682                warn!(target: LOG_CORE, "Unsupported module id: {id}, kind: {kind}");
683                continue;
684            };
685
686            decoders.insert(id, (kind.clone(), init.as_ref().decoder()));
687        }
688        Ok(ModuleDecoderRegistry::from(decoders))
689    }
690}
691
692/// Empty struct for if there are no params
693#[derive(Debug, Default, Clone, Serialize, Deserialize)]
694pub struct EmptyGenParams {}
695
696pub trait ModuleInitParams: serde::Serialize + serde::de::DeserializeOwned {
697    /// Locally configurable parameters for config generation
698    type Local: DeserializeOwned + Serialize;
699    /// Consensus parameters for config generation
700    type Consensus: DeserializeOwned + Serialize;
701
702    /// Assemble from the distinct parts
703    fn from_parts(local: Self::Local, consensus: Self::Consensus) -> Self;
704
705    /// Split the config into its distinct parts
706    fn to_parts(self) -> (Self::Local, Self::Consensus);
707}
708
709#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize, Encodable, Decodable)]
710pub struct ServerModuleConsensusConfig {
711    pub kind: ModuleKind,
712    pub version: ModuleConsensusVersion,
713    #[serde(with = "::hex::serde")]
714    pub config: Vec<u8>,
715}
716
717/// Config for the client-side of a particular Federation module
718///
719/// Since modules are (tbd.) pluggable into Federations,
720/// it needs to be some form of an abstract type-erased-like
721/// value.
722#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize, Encodable, Decodable)]
723pub struct ClientModuleConfig {
724    pub kind: ModuleKind,
725    pub version: ModuleConsensusVersion,
726    #[serde(with = "::fedimint_core::encoding::as_hex")]
727    pub config: DynRawFallback<DynClientConfig>,
728}
729
730impl ClientModuleConfig {
731    pub fn from_typed<T: fedimint_core::core::ClientConfig>(
732        module_instance_id: ModuleInstanceId,
733        kind: ModuleKind,
734        version: ModuleConsensusVersion,
735        value: T,
736    ) -> anyhow::Result<Self> {
737        Ok(Self {
738            kind,
739            version,
740            config: fedimint_core::core::DynClientConfig::from_typed(module_instance_id, value)
741                .into(),
742        })
743    }
744
745    pub fn redecode_raw(
746        self,
747        modules: &ModuleDecoderRegistry,
748    ) -> Result<Self, crate::encoding::DecodeError> {
749        Ok(Self {
750            config: self.config.redecode_raw(modules)?,
751            ..self
752        })
753    }
754
755    pub fn is_kind(&self, kind: &ModuleKind) -> bool {
756        &self.kind == kind
757    }
758
759    pub fn kind(&self) -> &ModuleKind {
760        &self.kind
761    }
762}
763
764impl ClientModuleConfig {
765    pub fn cast<T>(&self) -> anyhow::Result<&T>
766    where
767        T: 'static,
768    {
769        self.config
770            .expect_decoded_ref()
771            .as_any()
772            .downcast_ref::<T>()
773            .context("can't convert client module config to desired type")
774    }
775}
776
777/// Config for the server-side of a particular Federation module
778///
779/// See [`ClientModuleConfig`].
780#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
781pub struct ServerModuleConfig {
782    pub local: JsonWithKind,
783    pub private: JsonWithKind,
784    pub consensus: ServerModuleConsensusConfig,
785}
786
787impl ServerModuleConfig {
788    pub fn from(
789        local: JsonWithKind,
790        private: JsonWithKind,
791        consensus: ServerModuleConsensusConfig,
792    ) -> Self {
793        Self {
794            local,
795            private,
796            consensus,
797        }
798    }
799
800    pub fn to_typed<T: TypedServerModuleConfig>(&self) -> anyhow::Result<T> {
801        let local = serde_json::from_value(self.local.value().clone())?;
802        let private = serde_json::from_value(self.private.value().clone())?;
803        let consensus = <T::Consensus>::consensus_decode_whole(
804            &self.consensus.config[..],
805            &ModuleRegistry::default(),
806        )?;
807
808        Ok(TypedServerModuleConfig::from_parts(
809            local, private, consensus,
810        ))
811    }
812}
813
814/// Consensus-critical part of a server side module config
815pub trait TypedServerModuleConsensusConfig:
816    DeserializeOwned + Serialize + Encodable + Decodable
817{
818    fn kind(&self) -> ModuleKind;
819
820    fn version(&self) -> ModuleConsensusVersion;
821
822    fn from_erased(erased: &ServerModuleConsensusConfig) -> anyhow::Result<Self> {
823        Ok(Self::consensus_decode_whole(
824            &erased.config[..],
825            &ModuleRegistry::default(),
826        )?)
827    }
828}
829
830/// Module (server side) config, typed
831pub trait TypedServerModuleConfig: DeserializeOwned + Serialize {
832    /// Local non-consensus, not security-sensitive settings
833    type Local: DeserializeOwned + Serialize;
834    /// Private for this federation member data that are security sensitive and
835    /// will be encrypted at rest
836    type Private: DeserializeOwned + Serialize;
837    /// Shared consensus-critical config
838    type Consensus: TypedServerModuleConsensusConfig;
839
840    /// Assemble from the three functionally distinct parts
841    fn from_parts(local: Self::Local, private: Self::Private, consensus: Self::Consensus) -> Self;
842
843    /// Split the config into its three functionally distinct parts
844    fn to_parts(self) -> (ModuleKind, Self::Local, Self::Private, Self::Consensus);
845
846    /// Turn the typed config into type-erased version
847    fn to_erased(self) -> ServerModuleConfig {
848        let (kind, local, private, consensus) = self.to_parts();
849
850        ServerModuleConfig {
851            local: JsonWithKind::new(
852                kind.clone(),
853                serde_json::to_value(local).expect("serialization can't fail"),
854            ),
855            private: JsonWithKind::new(
856                kind,
857                serde_json::to_value(private).expect("serialization can't fail"),
858            ),
859            consensus: ServerModuleConsensusConfig {
860                kind: consensus.kind(),
861                version: consensus.version(),
862                config: consensus.consensus_encode_to_vec(),
863            },
864        }
865    }
866}
867
868/// Things that a `distributed_gen` config can send between peers
869#[derive(Serialize, Deserialize, Debug, Clone)]
870pub enum DkgPeerMessage {
871    DistributedGen(SupportedDkgMessage),
872    Encodable(Vec<u8>),
873    Completed,
874}
875
876/// Supported (by Fedimint's code) `DkgMessage<T>` types
877///
878/// Since `DkgMessage` is an open-set, yet we only use a subset of it,
879/// we can make a subset-trait to convert it to an `enum` that we
880/// it's easier to handle.
881///
882/// Candidate for refactoring after modularization effort is complete.
883pub trait ISupportedDkgMessage: Sized + Serialize + DeserializeOwned {
884    fn to_msg(self) -> SupportedDkgMessage;
885    fn from_msg(msg: SupportedDkgMessage) -> anyhow::Result<Self>;
886}
887
888/// `enum` version of [`SupportedDkgMessage`]
889#[derive(Serialize, Deserialize, Debug, Clone)]
890pub enum SupportedDkgMessage {
891    G1(DkgMessage<G1Projective>),
892    G2(DkgMessage<G2Projective>),
893}
894
895impl ISupportedDkgMessage for DkgMessage<G1Projective> {
896    fn to_msg(self) -> SupportedDkgMessage {
897        SupportedDkgMessage::G1(self)
898    }
899
900    fn from_msg(msg: SupportedDkgMessage) -> anyhow::Result<Self> {
901        match msg {
902            SupportedDkgMessage::G1(s) => Ok(s),
903            SupportedDkgMessage::G2(_) => bail!("Incorrect DkgGroup: G2"),
904        }
905    }
906}
907
908impl ISupportedDkgMessage for DkgMessage<G2Projective> {
909    fn to_msg(self) -> SupportedDkgMessage {
910        SupportedDkgMessage::G2(self)
911    }
912
913    fn from_msg(msg: SupportedDkgMessage) -> anyhow::Result<Self> {
914        match msg {
915            SupportedDkgMessage::G1(_) => bail!("Incorrect DkgGroup: G1"),
916            SupportedDkgMessage::G2(s) => Ok(s),
917        }
918    }
919}
920
921#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
922pub enum DkgMessage<G: DkgGroup> {
923    HashedCommit(Sha256),
924    Commit(#[serde(with = "serde_commit")] Vec<G>),
925    Share(
926        #[serde(with = "bls12_381_serde::scalar")] Scalar,
927        #[serde(with = "bls12_381_serde::scalar")] Scalar,
928    ),
929    Extract(#[serde(with = "serde_commit")] Vec<G>),
930}
931
932/// Defines a group (e.g. G1 or G2) that we can generate keys for
933pub trait DkgGroup:
934    Group + Mul<Scalar, Output = Self> + Curve + GroupEncoding + SGroup + Unpin
935{
936}
937
938impl<T: Group + Mul<Scalar, Output = T> + Curve + GroupEncoding + SGroup + Unpin> DkgGroup for T {}
939
940/// Handling the Group serialization with a wrapper
941mod serde_commit {
942    use serde::{Deserialize, Deserializer, Serialize, Serializer};
943
944    use crate::config::DkgGroup;
945
946    pub fn serialize<S: Serializer, G: DkgGroup>(vec: &[G], s: S) -> Result<S::Ok, S::Error> {
947        let wrap_vec: Vec<Wrap<G>> = vec.iter().copied().map(Wrap).collect();
948        wrap_vec.serialize(s)
949    }
950
951    pub fn deserialize<'d, D: Deserializer<'d>, G: DkgGroup>(d: D) -> Result<Vec<G>, D::Error> {
952        let wrap_vec = <Vec<Wrap<G>>>::deserialize(d)?;
953        Ok(wrap_vec.into_iter().map(|wrap| wrap.0).collect())
954    }
955
956    struct Wrap<G: DkgGroup>(G);
957
958    impl<G: DkgGroup> Serialize for Wrap<G> {
959        fn serialize<S: Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
960            self.0.serialize2(s)
961        }
962    }
963
964    impl<'d, G: DkgGroup> Deserialize<'d> for Wrap<G> {
965        fn deserialize<D: Deserializer<'d>>(d: D) -> Result<Self, D::Error> {
966            G::deserialize2(d).map(Wrap)
967        }
968    }
969}
970
971pub trait SGroup: Sized {
972    fn serialize2<S: Serializer>(&self, s: S) -> Result<S::Ok, S::Error>;
973    fn deserialize2<'d, D: Deserializer<'d>>(d: D) -> Result<Self, D::Error>;
974}
975
976impl SGroup for G2Projective {
977    fn serialize2<S: Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
978        bls12_381_serde::g2::serialize(&self.to_affine(), s)
979    }
980
981    fn deserialize2<'d, D: Deserializer<'d>>(d: D) -> Result<Self, D::Error> {
982        bls12_381_serde::g2::deserialize(d).map(Self::from)
983    }
984}
985
986impl SGroup for G1Projective {
987    fn serialize2<S: Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
988        bls12_381_serde::g1::serialize(&self.to_affine(), s)
989    }
990
991    fn deserialize2<'d, D: Deserializer<'d>>(d: D) -> Result<Self, D::Error> {
992        bls12_381_serde::g1::deserialize(d).map(Self::from)
993    }
994}
995
996/// Key under which the federation name can be sent to client in the `meta` part
997/// of the config
998pub const META_FEDERATION_NAME_KEY: &str = "federation_name";
999
1000pub fn load_from_file<T: DeserializeOwned>(path: &Path) -> Result<T, anyhow::Error> {
1001    let file = std::fs::File::open(path)?;
1002    Ok(serde_json::from_reader(file)?)
1003}
1004
1005pub mod serde_binary_human_readable {
1006    use std::borrow::Cow;
1007
1008    use hex::{FromHex, ToHex};
1009    use serde::de::DeserializeOwned;
1010    use serde::{Deserialize, Deserializer, Serialize, Serializer};
1011
1012    pub fn serialize<T: Serialize, S: Serializer>(x: &T, s: S) -> Result<S::Ok, S::Error> {
1013        if s.is_human_readable() {
1014            let bytes =
1015                bincode::serialize(x).map_err(|e| serde::ser::Error::custom(format!("{e:?}")))?;
1016            s.serialize_str(&bytes.encode_hex::<String>())
1017        } else {
1018            Serialize::serialize(x, s)
1019        }
1020    }
1021
1022    pub fn deserialize<'d, T: DeserializeOwned, D: Deserializer<'d>>(d: D) -> Result<T, D::Error> {
1023        if d.is_human_readable() {
1024            let hex_str: Cow<str> = Deserialize::deserialize(d)?;
1025            let bytes = Vec::from_hex(hex_str.as_ref()).map_err(serde::de::Error::custom)?;
1026            bincode::deserialize(&bytes).map_err(|e| serde::de::Error::custom(format!("{e:?}")))
1027        } else {
1028            Deserialize::deserialize(d)
1029        }
1030    }
1031}
1032
1033#[cfg(test)]
1034mod tests {
1035    use std::collections::BTreeMap;
1036
1037    use fedimint_core::config::{ClientConfig, GlobalClientConfig};
1038
1039    use crate::module::CoreConsensusVersion;
1040
1041    #[test]
1042    fn test_dcode_meta() {
1043        let config = ClientConfig {
1044            global: GlobalClientConfig {
1045                api_endpoints: BTreeMap::new(),
1046                broadcast_public_keys: None,
1047                consensus_version: CoreConsensusVersion { major: 0, minor: 0 },
1048                meta: vec![
1049                    ("foo".to_string(), "bar".to_string()),
1050                    ("baz".to_string(), "\"bam\"".to_string()),
1051                    ("arr".to_string(), "[\"1\", \"2\"]".to_string()),
1052                ]
1053                .into_iter()
1054                .collect(),
1055            },
1056            modules: BTreeMap::new(),
1057        };
1058
1059        assert_eq!(
1060            config
1061                .meta::<String>("foo")
1062                .expect("parsing legacy string failed"),
1063            Some("bar".to_string())
1064        );
1065        assert_eq!(
1066            config.meta::<String>("baz").expect("parsing string failed"),
1067            Some("bam".to_string())
1068        );
1069        assert_eq!(
1070            config
1071                .meta::<Vec<String>>("arr")
1072                .expect("parsing array failed"),
1073            Some(vec!["1".to_string(), "2".to_string()])
1074        );
1075
1076        assert!(config.meta::<Vec<String>>("foo").is_err());
1077        assert!(config.meta::<Vec<String>>("baz").is_err());
1078        assert_eq!(
1079            config
1080                .meta::<String>("arr")
1081                .expect("parsing via legacy fallback failed"),
1082            Some("[\"1\", \"2\"]".to_string())
1083        );
1084    }
1085}