Skip to main content

siwx_svm/
lib.rs

1//! # siwx-svm — Solana verification for Sign-In with X
2//!
3//! Implements CAIP-122 namespace profile for Solana:
4//! - **Ed25519** signature verification (`solana:ed25519`)
5//!
6//! # Quick start
7//!
8//! ```rust,no_run
9//! use siwx::SiwxMessage;
10//! use siwx_svm::{Ed25519Verifier, CHAIN_NAME};
11//! use siwx::Verifier;
12//!
13//! # async fn run() -> Result<(), Box<dyn std::error::Error>> {
14//! let message = SiwxMessage::new(
15//!     "example.com",
16//!     "GwAF45zjfyGzUbd3i3hXxzGeuchzEZXwpRYHZM5912F1",
17//!     "https://example.com/login",
18//!     "1",
19//!     "5eykt4UsFv8P8NJdTREpY1vzqKqZKvdpKuc147dw2N9d",
20//! )?;
21//! // let pubkey: [u8; 32] = ...; // Ed25519 public key
22//! // let sig_bytes: [u8; 64] = ...; // Ed25519 signature
23//! // Ed25519Verifier::new(pubkey).verify(&message, &sig_bytes).await?;
24//! # Ok(())
25//! # }
26//! ```
27
28mod ed25519;
29
30pub use ed25519::Ed25519Verifier;
31use siwx::{SiwxError, SiwxMessage};
32
33/// Human-readable chain name for the Solana namespace, used in the CAIP-122
34/// preamble line.
35pub const CHAIN_NAME: &str = "Solana";
36
37/// CAIP-122 signature type for Solana Ed25519.
38pub const SIG_TYPE: &str = "solana:ed25519";
39
40/// Convenience: format a [`SiwxMessage`] into the Solana CAIP-122 signing
41/// string.
42#[must_use]
43pub fn format_message(message: &SiwxMessage) -> String {
44    message.to_sign_string(CHAIN_NAME)
45}
46
47/// Validate that `address` is a valid base58-encoded Solana public key (32
48/// bytes decoded).
49///
50/// # Errors
51///
52/// Returns [`SiwxError::InvalidAddress`] if the format is wrong.
53pub fn validate_address(address: &str) -> Result<(), SiwxError> {
54    let bytes = bs58::decode(address)
55        .into_vec()
56        .map_err(|e| SiwxError::InvalidAddress(format!("invalid base58: {e}")))?;
57    if bytes.len() != 32 {
58        return Err(SiwxError::InvalidAddress(format!(
59            "expected 32 bytes, got {}",
60            bytes.len()
61        )));
62    }
63    Ok(())
64}
65
66#[cfg(test)]
67mod tests {
68    use super::*;
69
70    #[test]
71    fn validate_address_valid() {
72        assert!(validate_address("11111111111111111111111111111111").is_ok());
73        assert!(validate_address("GwAF45zjfyGzUbd3i3hXxzGeuchzEZXwpRYHZM5912F1").is_ok());
74    }
75
76    #[test]
77    fn validate_address_invalid() {
78        assert!(validate_address("not-valid").is_err());
79        assert!(validate_address("").is_err());
80    }
81
82    #[test]
83    fn format_message_preamble() {
84        let msg = SiwxMessage::new(
85            "example.com",
86            "GwAF45zjfyGzUbd3i3hXxzGeuchzEZXwpRYHZM5912F1",
87            "https://example.com",
88            "1",
89            "1",
90        )
91        .unwrap();
92        let text = format_message(&msg);
93        assert!(text.starts_with("example.com wants you to sign in with your Solana account:"));
94    }
95}