tap_caip/
validation.rs

1use crate::error::Error;
2use once_cell::sync::Lazy;
3use std::collections::HashMap;
4use std::fmt;
5use std::sync::{Arc, Mutex};
6
7/// Type for validator functions that validate addresses, references, etc.
8pub type ValidatorFn = fn(&str) -> Result<(), Box<dyn std::error::Error + Send + Sync>>;
9
10/// Registry for CAIP-specific validation functions
11pub struct ValidationRegistry {
12    chain_validators: HashMap<String, ValidatorFn>,
13    account_validators: HashMap<String, ValidatorFn>,
14    asset_validators: HashMap<String, ValidatorFn>,
15}
16
17impl Default for ValidationRegistry {
18    fn default() -> Self {
19        Self::new()
20    }
21}
22
23impl ValidationRegistry {
24    /// Create a new empty validation registry
25    pub fn new() -> Self {
26        Self {
27            chain_validators: HashMap::new(),
28            account_validators: HashMap::new(),
29            asset_validators: HashMap::new(),
30        }
31    }
32
33    /// Get access to the global ValidationRegistry singleton
34    pub fn global() -> Arc<Mutex<Self>> {
35        static REGISTRY: Lazy<Arc<Mutex<ValidationRegistry>>> = Lazy::new(|| {
36            let registry = ValidationRegistry::new_with_defaults();
37            Arc::new(Mutex::new(registry))
38        });
39        REGISTRY.clone()
40    }
41
42    /// Create a new validation registry with default validators
43    pub fn new_with_defaults() -> Self {
44        let mut registry = Self::new();
45
46        // Register Ethereum validators
47        registry.register_account_validator("eip155", ethereum_address_validator);
48        registry.register_asset_validator("erc20", ethereum_address_validator);
49        registry.register_asset_validator("erc721", ethereum_address_validator);
50
51        // Register Bitcoin validators
52        registry.register_account_validator("bip122", bitcoin_address_validator);
53
54        registry
55    }
56
57    /// Register a validator for chain IDs with the specified namespace
58    pub fn register_chain_validator(&mut self, namespace: &str, validator: ValidatorFn) {
59        self.chain_validators
60            .insert(namespace.to_string(), validator);
61    }
62
63    /// Register a validator for account addresses with the specified chain namespace
64    pub fn register_account_validator(&mut self, namespace: &str, validator: ValidatorFn) {
65        self.account_validators
66            .insert(namespace.to_string(), validator);
67    }
68
69    /// Register a validator for asset references with the specified asset namespace
70    pub fn register_asset_validator(&mut self, namespace: &str, validator: ValidatorFn) {
71        self.asset_validators
72            .insert(namespace.to_string(), validator);
73    }
74
75    /// Get a validator for chain IDs with the specified namespace
76    pub fn get_chain_validator(&self, namespace: &str) -> Option<ValidatorFn> {
77        self.chain_validators.get(namespace).copied()
78    }
79
80    /// Get a validator for account addresses with the specified chain namespace
81    pub fn get_account_validator(&self, namespace: &str) -> Option<ValidatorFn> {
82        self.account_validators.get(namespace).copied()
83    }
84
85    /// Get a validator for asset references with the specified asset namespace
86    pub fn get_asset_validator(&self, namespace: &str) -> Option<ValidatorFn> {
87        self.asset_validators.get(namespace).copied()
88    }
89}
90
91impl fmt::Debug for ValidationRegistry {
92    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
93        f.debug_struct("ValidationRegistry")
94            .field(
95                "chain_validators",
96                &format!("{} entries", self.chain_validators.len()),
97            )
98            .field(
99                "account_validators",
100                &format!("{} entries", self.account_validators.len()),
101            )
102            .field(
103                "asset_validators",
104                &format!("{} entries", self.asset_validators.len()),
105            )
106            .finish()
107    }
108}
109
110/// Validator for Ethereum addresses
111fn ethereum_address_validator(
112    address: &str,
113) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
114    // Ethereum addresses are 42 characters long (0x + 40 hex chars)
115    if !address.starts_with("0x") || address.len() != 42 {
116        return Err(Error::InvalidEthereumAddress(address.to_string()).into());
117    }
118
119    // Check if the address (after 0x) is valid hexadecimal
120    if hex::decode(&address[2..]).is_err() {
121        return Err(Error::InvalidEthereumAddress(address.to_string()).into());
122    }
123
124    // Additional validation could be added here, like checksum validation
125    Ok(())
126}
127
128/// Validator for Bitcoin addresses
129fn bitcoin_address_validator(
130    address: &str,
131) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
132    // Basic validation for now - implement more complete validation as needed
133    if address.len() < 26 || address.len() > 35 {
134        return Err(Error::InvalidBitcoinAddress(address.to_string()).into());
135    }
136
137    // Additional validation criteria could be added here
138    Ok(())
139}
140
141#[cfg(test)]
142mod tests {
143    use super::*;
144
145    #[test]
146    fn test_ethereum_validator() {
147        let valid_eth_address = "0x4b20993Bc481177ec7E8f571ceCaE8A9e22C02db";
148        let too_short_address = "0x4b20";
149        let not_hex_address = "0xZZZZ993Bc481177ec7E8f571ceCaE8A9e22C02db";
150        let no_prefix_address = "4b20993Bc481177ec7E8f571ceCaE8A9e22C02db";
151
152        assert!(ethereum_address_validator(valid_eth_address).is_ok());
153        assert!(ethereum_address_validator(too_short_address).is_err());
154        assert!(ethereum_address_validator(not_hex_address).is_err());
155        assert!(ethereum_address_validator(no_prefix_address).is_err());
156    }
157
158    #[test]
159    fn test_bitcoin_validator() {
160        let valid_btc_address = "1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa"; // Genesis block address
161        let too_short_address = "1A1zP";
162        let too_long_address = "1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa12345678901234";
163
164        assert!(bitcoin_address_validator(valid_btc_address).is_ok());
165        assert!(bitcoin_address_validator(too_short_address).is_err());
166        assert!(bitcoin_address_validator(too_long_address).is_err());
167    }
168
169    #[test]
170    fn test_registry() {
171        let registry = ValidationRegistry::new_with_defaults();
172
173        // Ethereum validators should be registered
174        assert!(registry.get_account_validator("eip155").is_some());
175        assert!(registry.get_asset_validator("erc20").is_some());
176        assert!(registry.get_asset_validator("erc721").is_some());
177
178        // Bitcoin validators should be registered
179        assert!(registry.get_account_validator("bip122").is_some());
180
181        // Non-registered validators should not be present
182        assert!(registry.get_account_validator("polkadot").is_none());
183        assert!(registry.get_asset_validator("unknown").is_none());
184    }
185
186    #[test]
187    fn test_global_registry() {
188        let registry = ValidationRegistry::global();
189        let registry_guard = registry.lock().unwrap();
190
191        // Global registry should have the default validators
192        assert!(registry_guard.get_account_validator("eip155").is_some());
193        assert!(registry_guard.get_asset_validator("erc20").is_some());
194    }
195}