Skip to main content

wallet_adapter_common/
utils.rs

1use std::{borrow::Cow, time::SystemTime};
2
3use ed25519_dalek::{Signature, Verifier, VerifyingKey};
4
5use crate::{WalletUtilsError, WalletUtilsResult};
6
7/// A 32 byte array representing a Public Key
8pub type PublicKeyBytes = [u8; 32];
9
10/// A 64 byte array representing a Signature
11pub type SignatureBytes = [u8; 64];
12
13/// Helper utilities
14pub struct WalletCommonUtils;
15
16impl WalletCommonUtils {
17    /// Generate a public key from random bytes. This is useful for testing
18    pub fn public_key_rand() -> [u8; 32] {
19        Self::rand_32bytes()
20    }
21
22    /// Generate a 32 byte array from random bytes
23    pub fn rand_32bytes() -> [u8; 32] {
24        use getrandom::SysRng;
25
26        use rand_chacha::ChaCha12Rng;
27        use rand_core::{Rng, SeedableRng};
28
29        let mut rng = ChaCha12Rng::try_from_rng(&mut SysRng).unwrap();
30        let mut buffer = [0u8; 32];
31
32        rng.fill_bytes(&mut buffer);
33
34        buffer
35    }
36
37    /// Parse a [PublicKey](VerifyingKey) from an array of 32 bytes
38    pub fn public_key(public_key_bytes: &[u8; 32]) -> WalletUtilsResult<VerifyingKey> {
39        VerifyingKey::from_bytes(public_key_bytes)
40            .or(Err(WalletUtilsError::InvalidEd25519PublicKeyBytes))
41    }
42
43    /// Parse a [Signature] from an array of 64 bytes
44    pub fn signature(signature_bytes: &[u8; 64]) -> Signature {
45        Signature::from_bytes(signature_bytes)
46    }
47
48    /// Convert a slice of bytes into a 32 byte array. This is useful especially if a [PublicKey](VerifyingKey) is
49    /// given as a slice instead of 32 byte array
50    pub fn to32byte_array(bytes: &[u8]) -> WalletUtilsResult<[u8; 32]> {
51        bytes
52            .try_into()
53            .or(Err(WalletUtilsError::Expected32ByteLength))
54    }
55
56    /// Convert a slice of bytes into a 64 byte array. This is useful especially if a [Signature] is
57    /// given as a slice instead of 64 byte array
58    pub fn to64byte_array(bytes: &[u8]) -> WalletUtilsResult<[u8; 64]> {
59        bytes
60            .try_into()
61            .or(Err(WalletUtilsError::Expected64ByteLength))
62    }
63
64    /// Verify a [message](str) using a [PublicKey](VerifyingKey) and [Signature]
65    pub fn verify_signature(
66        public_key: VerifyingKey,
67        message: &[u8],
68        signature: Signature,
69    ) -> WalletUtilsResult<()> {
70        public_key
71            .verify(message, &signature)
72            .or(Err(WalletUtilsError::InvalidSignature))
73    }
74
75    /// Verify a [message](str) using a [PublicKey](VerifyingKey) and [Signature]
76    pub fn verify(
77        public_key_bytes: &[u8; 32],
78        message_bytes: &[u8],
79        signature_bytes: &[u8; 64],
80    ) -> WalletUtilsResult<()> {
81        let public_key = Self::public_key(public_key_bytes)?;
82        let signature = Self::signature(signature_bytes);
83
84        public_key
85            .verify(message_bytes, &signature)
86            .or(Err(WalletUtilsError::InvalidSignature))
87    }
88
89    /// Generate the Base58 address from a [PublicKey](VerifyingKey)
90    pub fn address(public_key: VerifyingKey) -> String {
91        bs58::encode(public_key.as_ref()).into_string()
92    }
93
94    /// Generate a Base58 encoded string from a [Signature]
95    pub fn base58_signature(signature: Signature) -> String {
96        bs58::encode(signature.to_bytes()).into_string()
97    }
98
99    /// Get the shortened string of the `Base58 string` .
100    /// It displays the first 4 characters and the last for characters
101    /// separated by ellipsis eg `FXdl...RGd4` .
102    /// If the string is less than 8 characters, an error is thrown
103    pub fn shorten_base58<'a>(base58_str: &'a str) -> WalletUtilsResult<Cow<'a, str>> {
104        if base58_str.len() < 8 {
105            return Err(WalletUtilsError::InvalidBase58Address);
106        }
107
108        let first_part = &base58_str[..4];
109        let last_part = &base58_str[base58_str.len() - 4..];
110
111        Ok(Cow::Borrowed(first_part) + "..." + last_part)
112    }
113
114    /// Same as [Self::shorten_base58] but with a custom range
115    /// instead of taking the first 4 character and the last 4 characters
116    /// it uses a custom range.
117    pub fn custom_shorten_base58<'a>(
118        base58_str: &'a str,
119        take: usize,
120    ) -> WalletUtilsResult<Cow<'a, str>> {
121        if base58_str.len() < take + take {
122            return Err(WalletUtilsError::InvalidBase58Address);
123        }
124
125        let first_part = &base58_str[..take];
126        let last_part = &base58_str[base58_str.len() - take..];
127
128        Ok(Cow::Borrowed(first_part) + "..." + last_part)
129    }
130
131    /// Same as [Self::shorten_base58] but with a custom range
132    /// instead of taking the first 4 character and the last 4 characters
133    /// it uses a custom range for first characters before ellipsis and last characters after ellipsis.
134    pub fn custom_shorten_address_rl<'a>(
135        base58_address: &'a str,
136        left: usize,
137        right: usize,
138    ) -> WalletUtilsResult<Cow<'a, str>> {
139        if base58_address.len() < left + right {
140            return Err(WalletUtilsError::InvalidBase58Address);
141        }
142
143        let first_part = &base58_address[..left];
144        let last_part = &base58_address[base58_address.len() - right..];
145
146        Ok(Cow::Borrowed(first_part) + "..." + last_part)
147    }
148
149    /// Converts [SystemTime] to ISO 8601 datetime string as required by
150    /// Sign In With Solana standard
151    pub fn to_iso860(system_time: SystemTime) -> humantime::Rfc3339Timestamp {
152        humantime::format_rfc3339_millis(system_time)
153    }
154}