fedimint_core/net/
api_announcement.rs

1use std::collections::BTreeMap;
2
3use bitcoin::hashes::{sha256, Hash};
4use bitcoin::secp256k1::Message;
5use fedimint_core::db::DatabaseLookup;
6use fedimint_core::encoding::{Decodable, Encodable};
7use fedimint_core::task::MaybeSend;
8use fedimint_core::PeerId;
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        self.consensus_encode(&mut msg)
48            .expect("writing to vec is infallible");
49        sha256::Hash::hash(&msg)
50    }
51
52    pub fn sign<C: secp256k1::Signing>(
53        &self,
54        ctx: &secp256k1::Secp256k1<C>,
55        key: &secp256k1::Keypair,
56    ) -> SignedApiAnnouncement {
57        let msg = Message::from_digest(*self.tagged_hash().as_ref());
58        let signature = ctx.sign_schnorr(&msg, key);
59        SignedApiAnnouncement {
60            api_announcement: self.clone(),
61            signature,
62        }
63    }
64}
65
66impl SignedApiAnnouncement {
67    /// Returns true if the signature is valid for the given public key.
68    pub fn verify<C: secp256k1::Verification>(
69        &self,
70        ctx: &secp256k1::Secp256k1<C>,
71        pk: &secp256k1::PublicKey,
72    ) -> bool {
73        let msg = Message::from_digest(*self.api_announcement.tagged_hash().as_ref());
74        ctx.verify_schnorr(&self.signature, &msg, &pk.x_only_public_key().0)
75            .is_ok()
76    }
77}
78
79/// Override api URLs used by the client.
80///
81/// Takes a list of peer IDs and their API URLs, and overrides the URLs with the
82/// ones stored in the respective database. This function is generic so it can
83/// be used with both the client and server databases.
84pub async fn override_api_urls<P>(
85    db: &Database,
86    cfg_api_urls: impl IntoIterator<Item = (PeerId, SafeUrl)>,
87    db_key_prefix: &P,
88    key_to_peer_id: impl Fn(&P::Record) -> PeerId,
89) -> BTreeMap<PeerId, SafeUrl>
90where
91    P: DatabaseLookup + DatabaseKeyPrefix + MaybeSend + MaybeSync,
92    P::Record: DatabaseRecord<Value = SignedApiAnnouncement> + DatabaseKey + MaybeSend + MaybeSync,
93{
94    let mut db_api_urls = db
95        .begin_transaction_nc()
96        .await
97        .find_by_prefix(db_key_prefix)
98        .await
99        .map(|(key, announcement)| (key_to_peer_id(&key), announcement.api_announcement.api_url))
100        .collect::<BTreeMap<_, _>>()
101        .await;
102
103    cfg_api_urls
104        .into_iter()
105        .map(|(peer_id, cfg_api_url)| {
106            (peer_id, db_api_urls.remove(&peer_id).unwrap_or(cfg_api_url))
107        })
108        .collect::<BTreeMap<_, _>>()
109}