fedimint_core/net/
api_announcement.rs

1use std::collections::BTreeMap;
2
3use bitcoin::hashes::{Hash, sha256};
4use bitcoin::secp256k1::Message;
5use fedimint_core::PeerId;
6use fedimint_core::db::DatabaseLookup;
7use fedimint_core::encoding::{Decodable, Encodable};
8use fedimint_core::task::MaybeSend;
9use futures::StreamExt;
10use jsonrpsee_core::Serialize;
11use serde::Deserialize;
12
13use crate::db::{
14    Database, DatabaseKey, DatabaseKeyPrefix, DatabaseRecord, IDatabaseTransactionOpsCoreTyped,
15};
16use crate::task::MaybeSync;
17use crate::util::SafeUrl;
18
19const API_ANNOUNCEMENT_MESSAGE_TAG: &[u8] = b"fedimint-api-announcement";
20
21#[derive(Debug, Serialize, Deserialize, Clone, Eq, Hash, PartialEq, Encodable, Decodable)]
22pub struct ApiAnnouncement {
23    pub api_url: SafeUrl,
24    pub nonce: u64,
25}
26
27#[derive(Debug, Serialize, Deserialize, Clone, Eq, Hash, PartialEq, Encodable, Decodable)]
28pub struct SignedApiAnnouncement {
29    pub api_announcement: ApiAnnouncement,
30    pub signature: secp256k1::schnorr::Signature,
31}
32
33#[derive(Debug, Serialize, Deserialize, Clone, Eq, Hash, PartialEq, Encodable, Decodable)]
34pub struct SignedApiAnnouncementSubmission {
35    #[serde(flatten)]
36    pub signed_api_announcement: SignedApiAnnouncement,
37    pub peer_id: PeerId,
38}
39
40impl ApiAnnouncement {
41    pub fn new(api_url: SafeUrl, nonce: u64) -> Self {
42        Self { api_url, nonce }
43    }
44
45    pub fn tagged_hash(&self) -> sha256::Hash {
46        let mut msg = API_ANNOUNCEMENT_MESSAGE_TAG.to_vec();
47        msg.append(&mut self.consensus_encode_to_vec());
48        sha256::Hash::hash(&msg)
49    }
50
51    pub fn sign<C: secp256k1::Signing>(
52        &self,
53        ctx: &secp256k1::Secp256k1<C>,
54        key: &secp256k1::Keypair,
55    ) -> SignedApiAnnouncement {
56        let msg = Message::from_digest(*self.tagged_hash().as_ref());
57        let signature = ctx.sign_schnorr(&msg, key);
58        SignedApiAnnouncement {
59            api_announcement: self.clone(),
60            signature,
61        }
62    }
63}
64
65impl SignedApiAnnouncement {
66    /// Returns true if the signature is valid for the given public key.
67    pub fn verify<C: secp256k1::Verification>(
68        &self,
69        ctx: &secp256k1::Secp256k1<C>,
70        pk: &secp256k1::PublicKey,
71    ) -> bool {
72        let msg = Message::from_digest(*self.api_announcement.tagged_hash().as_ref());
73        ctx.verify_schnorr(&self.signature, &msg, &pk.x_only_public_key().0)
74            .is_ok()
75    }
76}
77
78/// Override api URLs used by the client.
79///
80/// Takes a list of peer IDs and their API URLs, and overrides the URLs with the
81/// ones stored in the respective database. This function is generic so it can
82/// be used with both the client and server databases.
83pub async fn override_api_urls<P>(
84    db: &Database,
85    cfg_api_urls: impl IntoIterator<Item = (PeerId, SafeUrl)>,
86    db_key_prefix: &P,
87    key_to_peer_id: impl Fn(&P::Record) -> PeerId,
88) -> BTreeMap<PeerId, SafeUrl>
89where
90    P: DatabaseLookup + DatabaseKeyPrefix + MaybeSend + MaybeSync,
91    P::Record: DatabaseRecord<Value = SignedApiAnnouncement> + DatabaseKey + MaybeSend + MaybeSync,
92{
93    let mut db_api_urls = db
94        .begin_transaction_nc()
95        .await
96        .find_by_prefix(db_key_prefix)
97        .await
98        .map(|(key, announcement)| (key_to_peer_id(&key), announcement.api_announcement.api_url))
99        .collect::<BTreeMap<_, _>>()
100        .await;
101
102    cfg_api_urls
103        .into_iter()
104        .map(|(peer_id, cfg_api_url)| {
105            (peer_id, db_api_urls.remove(&peer_id).unwrap_or(cfg_api_url))
106        })
107        .collect::<BTreeMap<_, _>>()
108}