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