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