Rust and WASM did-method-plc tools and structures
1//! WASM bindings for did:plc operations 2//! 3//! This module is only compiled when both: 4//! - Building for wasm32 target (`target_arch = "wasm32"`) 5//! - The "wasm" feature is enabled (provides wasm_bindgen and related dependencies) 6 7#![cfg(all(target_arch = "wasm32", feature = "wasm"))] 8 9use crate::builder::DidBuilder as NativeDidBuilder; 10use crate::crypto::{SigningKey as NativeSigningKey, VerifyingKey as NativeVerifyingKey}; 11use crate::did::Did as NativeDid; 12use crate::document::{DidDocument as NativeDidDocument, ServiceEndpoint as NativeServiceEndpoint}; 13use crate::operations::Operation as NativeOperation; 14use serde::{Deserialize, Serialize}; 15use wasm_bindgen::prelude::*; 16 17/// WASM wrapper for DID 18#[wasm_bindgen] 19pub struct WasmDid { 20 inner: NativeDid, 21} 22 23#[wasm_bindgen] 24impl WasmDid { 25 /// Parse and validate a DID string 26 /// 27 /// # Errors 28 /// 29 /// Throws a JavaScript error if the DID is invalid 30 #[wasm_bindgen(constructor)] 31 pub fn parse(did_string: &str) -> Result<WasmDid, JsValue> { 32 NativeDid::parse(did_string) 33 .map(|did| WasmDid { inner: did }) 34 .map_err(|e| JsValue::from_str(&e.to_string())) 35 } 36 37 /// Check if this DID is valid 38 /// 39 /// Since DIDs can only be constructed through validation, this always returns true 40 #[wasm_bindgen(js_name = "isValid")] 41 pub fn is_valid(&self) -> bool { 42 self.inner.is_valid() 43 } 44 45 /// Get the 24-character identifier portion (without "did:plc:" prefix) 46 #[wasm_bindgen(getter)] 47 pub fn identifier(&self) -> String { 48 self.inner.identifier().to_string() 49 } 50 51 /// Get the full DID string including "did:plc:" prefix 52 #[wasm_bindgen(js_name = "toString")] 53 pub fn to_string_js(&self) -> String { 54 self.inner.to_string() 55 } 56 57 /// Get the full DID string as a getter 58 #[wasm_bindgen(getter)] 59 pub fn did(&self) -> String { 60 self.inner.to_string() 61 } 62} 63 64/// WASM wrapper for SigningKey 65#[wasm_bindgen] 66pub struct WasmSigningKey { 67 inner: NativeSigningKey, 68} 69 70#[wasm_bindgen] 71impl WasmSigningKey { 72 /// Generate a new P-256 key pair 73 #[wasm_bindgen(js_name = "generateP256")] 74 pub fn generate_p256() -> WasmSigningKey { 75 WasmSigningKey { 76 inner: NativeSigningKey::generate_p256(), 77 } 78 } 79 80 /// Generate a new secp256k1 key pair 81 #[wasm_bindgen(js_name = "generateK256")] 82 pub fn generate_k256() -> WasmSigningKey { 83 WasmSigningKey { 84 inner: NativeSigningKey::generate_k256(), 85 } 86 } 87 88 /// Convert this signing key to a did:key string 89 #[wasm_bindgen(js_name = "toDidKey")] 90 pub fn to_did_key(&self) -> String { 91 self.inner.to_did_key() 92 } 93} 94 95/// WASM wrapper for ServiceEndpoint 96#[wasm_bindgen] 97#[derive(Serialize, Deserialize)] 98pub struct WasmServiceEndpoint { 99 /// Service type (e.g., "AtprotoPersonalDataServer") 100 #[wasm_bindgen(skip)] 101 pub service_type: String, 102 /// Service endpoint URL 103 #[wasm_bindgen(skip)] 104 pub endpoint: String, 105} 106 107#[wasm_bindgen] 108impl WasmServiceEndpoint { 109 /// Create a new service endpoint 110 #[wasm_bindgen(constructor)] 111 pub fn new(service_type: String, endpoint: String) -> WasmServiceEndpoint { 112 WasmServiceEndpoint { 113 service_type, 114 endpoint, 115 } 116 } 117 118 /// Get the service type 119 #[wasm_bindgen(getter = serviceType)] 120 pub fn service_type(&self) -> String { 121 self.service_type.clone() 122 } 123 124 /// Get the endpoint URL 125 #[wasm_bindgen(getter)] 126 pub fn endpoint(&self) -> String { 127 self.endpoint.clone() 128 } 129} 130 131impl From<WasmServiceEndpoint> for NativeServiceEndpoint { 132 fn from(wasm: WasmServiceEndpoint) -> Self { 133 NativeServiceEndpoint::new(wasm.service_type, wasm.endpoint) 134 } 135} 136 137/// WASM wrapper for DidBuilder 138#[wasm_bindgen] 139pub struct WasmDidBuilder { 140 inner: NativeDidBuilder, 141} 142 143#[wasm_bindgen] 144impl WasmDidBuilder { 145 /// Create a new DID builder 146 #[wasm_bindgen(constructor)] 147 pub fn new() -> WasmDidBuilder { 148 WasmDidBuilder { 149 inner: NativeDidBuilder::new(), 150 } 151 } 152 153 /// Add a rotation key 154 #[wasm_bindgen(js_name = "addRotationKey")] 155 pub fn add_rotation_key(mut self, key: WasmSigningKey) -> WasmDidBuilder { 156 self.inner = self.inner.add_rotation_key(key.inner); 157 self 158 } 159 160 /// Add a verification method 161 #[wasm_bindgen(js_name = "addVerificationMethod")] 162 pub fn add_verification_method(mut self, name: String, key: WasmSigningKey) -> WasmDidBuilder { 163 self.inner = self.inner.add_verification_method(name, key.inner); 164 self 165 } 166 167 /// Add an also-known-as URI 168 #[wasm_bindgen(js_name = "addAlsoKnownAs")] 169 pub fn add_also_known_as(mut self, uri: String) -> WasmDidBuilder { 170 self.inner = self.inner.add_also_known_as(uri); 171 self 172 } 173 174 /// Add a service endpoint 175 #[wasm_bindgen(js_name = "addService")] 176 pub fn add_service(mut self, name: String, endpoint: WasmServiceEndpoint) -> WasmDidBuilder { 177 self.inner = self.inner.add_service(name, endpoint.into()); 178 self 179 } 180 181 /// Build and sign the genesis operation 182 /// 183 /// Returns a JavaScript object with: 184 /// - did: The created DID string 185 /// - operation: The signed genesis operation as JSON 186 /// 187 /// # Errors 188 /// 189 /// Throws a JavaScript error if building fails 190 #[wasm_bindgen] 191 pub fn build(self) -> Result<JsValue, JsValue> { 192 let (did, operation, _keys) = self 193 .inner 194 .build() 195 .map_err(|e| JsValue::from_str(&e.to_string()))?; 196 197 // Create a result object 198 let result = js_sys::Object::new(); 199 200 // Set the DID 201 js_sys::Reflect::set( 202 &result, 203 &JsValue::from_str("did"), 204 &JsValue::from_str(did.as_str()), 205 ) 206 .map_err(|e| JsValue::from_str(&format!("Failed to set did: {:?}", e)))?; 207 208 // Serialize the operation to JSON 209 let operation_json = serde_json::to_string(&operation) 210 .map_err(|e| JsValue::from_str(&e.to_string()))?; 211 212 js_sys::Reflect::set( 213 &result, 214 &JsValue::from_str("operation"), 215 &JsValue::from_str(&operation_json), 216 ) 217 .map_err(|e| JsValue::from_str(&format!("Failed to set operation: {:?}", e)))?; 218 219 Ok(result.into()) 220 } 221} 222 223/// WASM wrapper for DID Document 224#[wasm_bindgen] 225pub struct WasmDidDocument { 226 inner: NativeDidDocument, 227} 228 229#[wasm_bindgen] 230impl WasmDidDocument { 231 /// Parse a DID document from JSON 232 #[wasm_bindgen(js_name = "fromJson")] 233 pub fn from_json(json: &str) -> Result<WasmDidDocument, JsValue> { 234 let doc: NativeDidDocument = 235 serde_json::from_str(json).map_err(|e| JsValue::from_str(&e.to_string()))?; 236 237 Ok(WasmDidDocument { inner: doc }) 238 } 239 240 /// Convert this DID document to JSON 241 #[wasm_bindgen(js_name = "toJson")] 242 pub fn to_json(&self) -> Result<String, JsValue> { 243 serde_json::to_string_pretty(&self.inner).map_err(|e| JsValue::from_str(&e.to_string())) 244 } 245 246 /// Get the DID this document describes 247 #[wasm_bindgen(getter)] 248 pub fn id(&self) -> String { 249 self.inner.id.to_string() 250 } 251 252 /// Validate this DID document 253 #[wasm_bindgen] 254 pub fn validate(&self) -> Result<(), JsValue> { 255 self.inner 256 .validate() 257 .map_err(|e| JsValue::from_str(&e.to_string())) 258 } 259} 260 261/// WASM wrapper for VerifyingKey 262#[wasm_bindgen] 263pub struct WasmVerifyingKey { 264 inner: NativeVerifyingKey, 265} 266 267#[wasm_bindgen] 268impl WasmVerifyingKey { 269 /// Create a verifying key from a did:key string 270 #[wasm_bindgen(js_name = "fromDidKey")] 271 pub fn from_did_key(did_key: &str) -> Result<WasmVerifyingKey, JsValue> { 272 NativeVerifyingKey::from_did_key(did_key) 273 .map(|key| WasmVerifyingKey { inner: key }) 274 .map_err(|e| JsValue::from_str(&e.to_string())) 275 } 276 277 /// Convert this verifying key to a did:key string 278 #[wasm_bindgen(js_name = "toDidKey")] 279 pub fn to_did_key(&self) -> String { 280 self.inner.to_did_key() 281 } 282} 283 284/// WASM wrapper for Operation 285#[wasm_bindgen] 286pub struct WasmOperation { 287 inner: NativeOperation, 288} 289 290#[wasm_bindgen] 291impl WasmOperation { 292 /// Parse an operation from JSON 293 #[wasm_bindgen(js_name = "fromJson")] 294 pub fn from_json(json: &str) -> Result<WasmOperation, JsValue> { 295 let op: NativeOperation = 296 serde_json::from_str(json).map_err(|e| JsValue::from_str(&e.to_string()))?; 297 298 Ok(WasmOperation { inner: op }) 299 } 300 301 /// Convert this operation to JSON 302 #[wasm_bindgen(js_name = "toJson")] 303 pub fn to_json(&self) -> Result<String, JsValue> { 304 serde_json::to_string_pretty(&self.inner).map_err(|e| JsValue::from_str(&e.to_string())) 305 } 306 307 /// Check if this is a genesis operation 308 #[wasm_bindgen(js_name = "isGenesis")] 309 pub fn is_genesis(&self) -> bool { 310 self.inner.is_genesis() 311 } 312 313 /// Get the CID of this operation 314 #[wasm_bindgen] 315 pub fn cid(&self) -> Result<String, JsValue> { 316 self.inner 317 .cid() 318 .map_err(|e| JsValue::from_str(&e.to_string())) 319 } 320 321 /// Get the prev field (CID of previous operation), or null for genesis 322 #[wasm_bindgen] 323 pub fn prev(&self) -> Option<String> { 324 self.inner.prev().map(|s| s.to_string()) 325 } 326 327 /// Get the signature of this operation 328 #[wasm_bindgen] 329 pub fn signature(&self) -> String { 330 self.inner.signature().to_string() 331 } 332 333 /// Get the rotation keys from this operation (if any) 334 #[wasm_bindgen(js_name = "rotationKeys")] 335 pub fn rotation_keys(&self) -> Option<Vec<String>> { 336 self.inner.rotation_keys().map(|keys| keys.to_vec()) 337 } 338 339 /// Verify this operation's signature using the provided rotation keys 340 /// 341 /// Returns true if the signature is valid with at least one of the keys 342 #[wasm_bindgen] 343 pub fn verify(&self, rotation_keys: Vec<WasmVerifyingKey>) -> Result<bool, JsValue> { 344 let native_keys: Vec<NativeVerifyingKey> = 345 rotation_keys.into_iter().map(|k| k.inner).collect(); 346 347 match self.inner.verify(&native_keys) { 348 Ok(_) => Ok(true), 349 Err(_) => Ok(false), 350 } 351 } 352 353 /// Verify this operation and return which key index verified it (0-based) 354 /// 355 /// Returns the index of the rotation key that verified the signature, 356 /// or throws an error if none verified 357 #[wasm_bindgen(js_name = "verifyWithKeyIndex")] 358 pub fn verify_with_key_index(&self, rotation_keys: Vec<WasmVerifyingKey>) -> Result<usize, JsValue> { 359 for (i, key) in rotation_keys.iter().enumerate() { 360 if self.inner.verify(&[key.inner]).is_ok() { 361 return Ok(i); 362 } 363 } 364 Err(JsValue::from_str("No rotation key verified the signature")) 365 } 366} 367 368/// Initialize the WASM module 369/// 370/// This should be called before using any other functions 371#[wasm_bindgen(start)] 372pub fn init() { 373 // WASM module initialization 374 // For better panic messages in development, consider adding console_error_panic_hook 375}