nym_api_requests/
signable.rs

1// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
2// SPDX-License-Identifier: Apache-2.0
3
4use nym_crypto::asymmetric::ed25519;
5use nym_crypto::asymmetric::ed25519::serde_helpers::bs58_ed25519_signature;
6use serde::{Deserialize, Serialize};
7use utoipa::ToSchema;
8
9// the trait is not public as it's only defined on types that are guaranteed to not panic when serialised
10pub trait SignableMessageBody: Serialize + sealed::Sealed {
11    fn sign(self, key: &ed25519::PrivateKey) -> SignedMessage<Self>
12    where
13        Self: Sized,
14    {
15        let signature = key.sign(self.plaintext());
16        SignedMessage {
17            body: self,
18            signature,
19        }
20    }
21
22    fn plaintext(&self) -> Vec<u8> {
23        #[allow(clippy::unwrap_used)]
24        // SAFETY: all types that implement this trait have valid serialisations
25        serde_json::to_vec(&self).unwrap()
26    }
27}
28
29impl<T> SignableMessageBody for T where T: Serialize + sealed::Sealed {}
30
31#[derive(Clone, Serialize, Deserialize, Debug, ToSchema)]
32#[serde(rename_all = "camelCase")]
33pub struct SignedMessage<T> {
34    pub body: T,
35    #[schema(value_type = String)]
36    #[serde(with = "bs58_ed25519_signature")]
37    pub signature: ed25519::Signature,
38}
39
40impl<T> SignedMessage<T> {
41    pub fn verify_signature(&self, pub_key: &ed25519::PublicKey) -> bool
42    where
43        T: SignableMessageBody,
44    {
45        let plaintext = self.body.plaintext();
46        if plaintext.is_empty() {
47            return false;
48        }
49
50        pub_key.verify(&plaintext, &self.signature).is_ok()
51    }
52}
53
54// make sure only our types can implement this trait (to ensure infallible serialisation)
55pub(crate) mod sealed {
56    use crate::ecash::models::*;
57    use crate::models::{
58        ChainBlocksStatusResponseBody, DetailedSignersStatusResponseBody, SignersStatusResponseBody,
59    };
60
61    pub trait Sealed {}
62
63    // requests
64    impl Sealed for IssuedTicketbooksChallengeCommitmentRequestBody {}
65    impl Sealed for IssuedTicketbooksDataRequestBody {}
66
67    // responses
68    impl Sealed for IssuedTicketbooksChallengeCommitmentResponseBody {}
69    impl Sealed for IssuedTicketbooksForResponseBody {}
70    impl Sealed for IssuedTicketbooksDataResponseBody {}
71    impl Sealed for EcashSignerStatusResponseBody {}
72    impl Sealed for ChainBlocksStatusResponseBody {}
73    impl Sealed for SignersStatusResponseBody {}
74    impl Sealed for DetailedSignersStatusResponseBody {}
75}