Skip to main content

fedimint_server/net/
iroh.rs

1use std::net::SocketAddr;
2
3use anyhow::Context as _;
4use fedimint_core::envs::{
5    FM_IROH_DHT_ENABLE_ENV, FM_IROH_N0_DISCOVERY_ENABLE_ENV, FM_IROH_PKARR_PUBLISHER_ENABLE_ENV,
6    FM_IROH_PKARR_RESOLVER_ENABLE_ENV, FM_IROH_RELAYS_ENABLE_ENV, is_env_var_set,
7    is_env_var_set_opt,
8};
9use fedimint_core::net::iroh::{IROH_IDLE_TIMEOUT, IROH_KEEP_ALIVE_INTERVAL};
10use fedimint_core::secp256k1::SecretKey;
11use fedimint_core::util::SafeUrl;
12use fedimint_derive_secret::{ChildId, DerivableSecret};
13use fedimint_logging::LOG_NET_IROH;
14use iroh_next::address_lookup::{DnsAddressLookup, PkarrPublisher, PkarrResolver};
15use iroh_next::endpoint::QuicTransportConfig;
16use iroh_next::endpoint::presets::Minimal;
17use iroh_next::{Endpoint, RelayMode};
18use tracing::{debug, info, warn};
19
20/// Child key used for the Iroh 1.0 API endpoint.
21const IROH_V1_API_CHILD_ID: ChildId = ChildId(0);
22
23/// Derive the Iroh 1.0 API secret key from the guardian broadcast key.
24pub(crate) fn derive_iroh_v1_api_secret_key(broadcast_sk: &SecretKey) -> iroh_next::SecretKey {
25    let root = DerivableSecret::new_root(&broadcast_sk.secret_bytes(), b"fedimint-iroh-next");
26    let seed: [u8; 32] = root.child_key(IROH_V1_API_CHILD_ID).to_random_bytes();
27    iroh_next::SecretKey::from_bytes(&seed)
28}
29
30/// Build an Iroh 1.0 server endpoint.
31pub(crate) async fn build_iroh_v1_endpoint(
32    secret_key: iroh_next::SecretKey,
33    bind_addr: SocketAddr,
34    iroh_dns: Option<SafeUrl>,
35    alpn: &[u8],
36) -> anyhow::Result<Endpoint> {
37    let relay_mode = if is_env_var_set_opt(FM_IROH_RELAYS_ENABLE_ENV).unwrap_or(true) {
38        RelayMode::Default
39    } else {
40        warn!(target: LOG_NET_IROH, "Iroh 1.0 relays are disabled");
41        RelayMode::Disabled
42    };
43
44    let mut builder = Endpoint::builder(Minimal);
45
46    if let Some(iroh_dns) = iroh_dns.map(SafeUrl::to_unsafe) {
47        if is_env_var_set_opt(FM_IROH_PKARR_PUBLISHER_ENABLE_ENV).unwrap_or(true) {
48            builder = builder.address_lookup(PkarrPublisher::builder(iroh_dns.clone()));
49        } else {
50            warn!(target: LOG_NET_IROH, "Iroh 1.0 pkarr publisher is disabled");
51        }
52
53        if is_env_var_set_opt(FM_IROH_PKARR_RESOLVER_ENABLE_ENV).unwrap_or(true) {
54            builder = builder.address_lookup(PkarrResolver::builder(iroh_dns));
55        } else {
56            warn!(target: LOG_NET_IROH, "Iroh 1.0 pkarr resolver is disabled");
57        }
58    }
59
60    if is_env_var_set(FM_IROH_DHT_ENABLE_ENV) {
61        debug!(target: LOG_NET_IROH, "Iroh 1.0 DHT is enabled");
62        builder = builder.address_lookup(iroh_mainline_address_lookup::DhtAddressLookup::builder());
63    } else {
64        info!(target: LOG_NET_IROH, "Iroh 1.0 DHT is disabled");
65    }
66
67    if is_env_var_set_opt(FM_IROH_N0_DISCOVERY_ENABLE_ENV).unwrap_or(true) {
68        if is_env_var_set_opt(FM_IROH_PKARR_PUBLISHER_ENABLE_ENV).unwrap_or(true) {
69            builder = builder.address_lookup(PkarrPublisher::n0_dns());
70        }
71        builder = builder.address_lookup(DnsAddressLookup::n0_dns());
72
73        if is_env_var_set_opt(FM_IROH_PKARR_RESOLVER_ENABLE_ENV).unwrap_or(true) {
74            builder = builder.address_lookup(PkarrResolver::n0_dns());
75        }
76    } else {
77        warn!(target: LOG_NET_IROH, "Iroh 1.0 n0 discovery is disabled");
78    }
79
80    let transport_config = QuicTransportConfig::builder()
81        .max_idle_timeout(Some(
82            IROH_IDLE_TIMEOUT
83                .try_into()
84                .expect("idle timeout fits in IdleTimeout"),
85        ))
86        .keep_alive_interval(IROH_KEEP_ALIVE_INTERVAL)
87        .build();
88
89    let endpoint = Box::pin(
90        builder
91            .relay_mode(relay_mode)
92            .secret_key(secret_key)
93            .alpns(vec![alpn.to_vec()])
94            .transport_config(transport_config)
95            .clear_ip_transports()
96            .bind_addr(bind_addr)
97            .context("Invalid Iroh 1.0 bind address")?
98            .bind(),
99    )
100    .await
101    .context("Failed to bind Iroh 1.0 endpoint")?;
102
103    info!(
104        target: LOG_NET_IROH,
105        %bind_addr,
106        endpoint_id = %endpoint.id(),
107        endpoint_id_pkarr = %z32::encode(endpoint.id().as_bytes()),
108        "Iroh 1.0 API server endpoint"
109    );
110
111    Ok(endpoint)
112}
113
114#[cfg(test)]
115mod tests {
116    use fedimint_core::secp256k1::SecretKey;
117
118    use super::derive_iroh_v1_api_secret_key;
119
120    #[test]
121    fn iroh_v1_api_key_derivation_is_stable() {
122        let broadcast_sk = SecretKey::from_slice(&[1; 32]).expect("valid test key");
123        assert_eq!(
124            derive_iroh_v1_api_secret_key(&broadcast_sk)
125                .public()
126                .to_string(),
127            "e4b678498c23a7444ac2daf4aed336e88c2fa51c10e973f4ec57ae493e25fcf3"
128        );
129    }
130}