fedimint_server/net/
iroh.rs1use 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
20const IROH_V1_API_CHILD_ID: ChildId = ChildId(0);
22
23pub(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
30pub(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}