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