wallet_adapter/wallet_ser_der/standard_features/
sign_message.rs

1use ed25519_dalek::{Signature, VerifyingKey};
2use wallet_adapter_common::WalletCommonUtils;
3use web_sys::{js_sys, wasm_bindgen::JsValue};
4
5use core::str;
6
7use crate::{
8    InnerUtils, Reflection, SemverVersion, StandardFunction, WalletAccount, WalletError,
9    WalletResult,
10};
11
12/// `solana:signMessage` containing the `version` and `callback` within
13/// the [StandardFunction] field
14#[derive(Debug, Clone, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
15pub struct SignMessage(pub(crate) StandardFunction);
16
17impl SignMessage {
18    /// Parse the callback for `solana:signMessage` from the [JsValue]
19    pub(crate) fn new(reflection: &Reflection, version: SemverVersion) -> WalletResult<Self> {
20        Ok(Self(StandardFunction::new(
21            reflection,
22            version,
23            "signMessage",
24            "solana",
25        )?))
26    }
27
28    /// Internal callback to request a browser wallet to sign a message
29    pub(crate) async fn call_sign_message<'a>(
30        &self,
31        wallet_account: &WalletAccount,
32        message: &'a [u8],
33    ) -> WalletResult<SignedMessageOutput<'a>> {
34        let message_value: js_sys::Uint8Array = message.into();
35
36        let mut message_object = Reflection::new_object();
37        message_object.set_object(&"account".into(), &wallet_account.js_value)?;
38        message_object.set_object(&"message".into(), &message_value)?;
39
40        // Call the callback with message and account
41        let outcome = self
42            .0
43            .callback
44            .call1(&JsValue::null(), message_object.get_inner())?;
45
46        let outcome = js_sys::Promise::resolve(&outcome);
47        let signed_message_result = wasm_bindgen_futures::JsFuture::from(outcome).await?;
48        let incase_of_error = Err(WalletError::InternalError(format!(
49            "solana:signedMessage -> SignedMessageOutput: Casting `{signed_message_result:?}` did not yield a Uini8Array"
50        )));
51
52        let signed_message_result = Reflection::new(signed_message_result)?
53            .into_array()
54            .or(incase_of_error)?
55            .to_vec();
56
57        if let Some(inner) = signed_message_result.first() {
58            let reflect_outcome = Reflection::new(inner.clone())?;
59            let signed_message = reflect_outcome.reflect_inner("signedMessage")?;
60            let signature_value = reflect_outcome.reflect_inner("signature")?;
61
62            let incase_of_error = Err(WalletError::InternalError(format!(
63                "solana:signedMessage -> SignedMessageOutput::signedMessage: Cast `{signed_message:?}` did not yield a JsValue"
64            )));
65
66            let signed_message = Reflection::new(signed_message)?
67                .into_bytes()
68                .or(incase_of_error)?
69                .to_vec();
70
71            if signed_message != message {
72                return Err(WalletError::SignedMessageMismatch);
73            }
74
75            let signature = InnerUtils::jsvalue_to_signature(
76                signature_value,
77                "solana::signMessage -> SignedMessageOutput::signature",
78            )?;
79
80            let public_key = WalletCommonUtils::public_key(&wallet_account.account.public_key)?;
81
82            WalletCommonUtils::verify_signature(public_key, message, signature)?;
83
84            Ok(SignedMessageOutput {
85                message,
86                public_key: wallet_account.account.public_key,
87                signature: signature.to_bytes(),
88            })
89        } else {
90            Err(WalletError::ReceivedAnEmptySignedMessagesArray)
91        }
92    }
93}
94
95/// The output of a signed message
96#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Copy, Clone)]
97pub struct SignedMessageOutput<'a> {
98    message: &'a [u8],
99    public_key: [u8; 32],
100    signature: [u8; 64],
101}
102
103impl SignedMessageOutput<'_> {
104    /// Get the message as a [UTF-8 str](core::str)
105    pub fn message(&self) -> &str {
106        //Should never fail since verified message is always UTF-8 Format hence `.unwrap()` is used.
107        // This is verified to be the input message where the input message is always UTF-8 encoded
108        str::from_utf8(self.message).unwrap()
109    }
110
111    /// Get the public key as an [Ed25519 Public Key](VerifyingKey)
112    pub fn public_key(&self) -> WalletResult<VerifyingKey> {
113        Ok(WalletCommonUtils::public_key(&self.public_key)?)
114    }
115
116    /// Get the Base58 address of the  [Ed25519 Public Key](VerifyingKey) that signed the message
117    pub fn address(&self) -> WalletResult<String> {
118        Ok(WalletCommonUtils::address(self.public_key()?))
119    }
120
121    /// Get the [Ed25519 Signature](Signature) that was generated when
122    /// the [Ed25519 Public Key](VerifyingKey) signed the UTF-8 encoded message
123    pub fn signature(&self) -> Signature {
124        WalletCommonUtils::signature(&self.signature)
125    }
126
127    /// Get the  [Ed25519 Signature](Signature) encoded in Base58 format
128    pub fn base58_signature(&self) -> WalletResult<String> {
129        Ok(WalletCommonUtils::base58_signature(self.signature()))
130    }
131}
132
133impl Default for SignedMessageOutput<'_> {
134    fn default() -> Self {
135        Self {
136            message: &[],
137            public_key: [0u8; 32],
138            signature: [0u8; 64],
139        }
140    }
141}