An easy-to-host PDS on the ATProtocol, iPhone and MacOS. Maintain control of your keys and data, always.
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat(ipc): expose get_or_create_device_key and sign_with_device_key Tauri commands

authored by

Malpercio and committed by
Tangled
50cbb9b7 27d83ebc

+97 -1
+22
apps/identity-wallet/src-tauri/src/device_key.rs
··· 10 10 // ── Public types ────────────────────────────────────────────────────────────── 11 11 12 12 #[derive(Debug, Serialize)] 13 + #[serde(rename_all = "camelCase")] 13 14 pub struct DevicePublicKey { 14 15 /// Multibase base58btc-encoded compressed P-256 public key point. 15 16 /// Format: 'z' + base58btc(33-byte SEC1 compressed point). ··· 361 362 let json3 = serde_json::to_value(&err3).unwrap(); 362 363 assert_eq!(json3["code"], "KEYCHAIN_ERROR"); 363 364 assert_eq!(json3["message"], "os error"); 365 + } 366 + 367 + // Ensures DevicePublicKey serializes key_id as keyId (camelCase) for Tauri IPC. 368 + // Without #[serde(rename_all = "camelCase")], this test fails. 369 + #[test] 370 + fn device_public_key_serializes_camel_case() { 371 + let key = DevicePublicKey { 372 + multibase: "zTest".into(), 373 + key_id: "did:key:zTest".into(), 374 + }; 375 + let json = serde_json::to_value(&key).unwrap(); 376 + assert_eq!(json["multibase"], "zTest"); 377 + assert_eq!( 378 + json["keyId"], "did:key:zTest", 379 + "key_id must serialize as keyId for TypeScript" 380 + ); 381 + // Confirm the snake_case version is NOT present. 382 + assert!( 383 + json.get("key_id").is_none(), 384 + "key_id must not appear as snake_case in JSON" 385 + ); 364 386 } 365 387 }
+16 -1
apps/identity-wallet/src-tauri/src/lib.rs
··· 179 179 } 180 180 } 181 181 182 + #[tauri::command] 183 + async fn get_or_create_device_key( 184 + ) -> Result<device_key::DevicePublicKey, device_key::DeviceKeyError> { 185 + device_key::get_or_create() 186 + } 187 + 188 + #[tauri::command] 189 + async fn sign_with_device_key(data: Vec<u8>) -> Result<Vec<u8>, device_key::DeviceKeyError> { 190 + device_key::sign(&data) 191 + } 192 + 182 193 #[cfg_attr(mobile, tauri::mobile_entry_point)] 183 194 pub fn run() { 184 195 tauri::Builder::default() 185 - .invoke_handler(tauri::generate_handler![create_account]) 196 + .invoke_handler(tauri::generate_handler![ 197 + create_account, 198 + get_or_create_device_key, 199 + sign_with_device_key, 200 + ]) 186 201 .run(tauri::generate_context!()) 187 202 .expect("error while running tauri application"); 188 203 }
+59
apps/identity-wallet/src/lib/ipc.ts
··· 45 45 params: CreateAccountParams 46 46 ): Promise<CreateAccountResult> => 47 47 invoke('create_account', params); 48 + 49 + // ── Device Key types ────────────────────────────────────────────────────────── 50 + 51 + /** 52 + * Device public key returned by the `get_or_create_device_key` Rust command. 53 + * Matches DevicePublicKey struct with #[serde(rename_all = "camelCase")]. 54 + */ 55 + export type DevicePublicKey = { 56 + /** 'z' + base58btc(33-byte compressed P-256 public key point). */ 57 + multibase: string; 58 + /** Full did:key URI: 'did:key:z...' */ 59 + keyId: string; 60 + }; 61 + 62 + /** 63 + * Error returned by device key commands. 64 + * 65 + * Serialized as `{ code: "KEY_GENERATION_FAILED" }` etc. by the Rust backend. 66 + * `message` is present only for KEYCHAIN_ERROR. 67 + */ 68 + export type DeviceKeyError = { 69 + code: 70 + | 'KEY_GENERATION_FAILED' 71 + | 'KEY_NOT_FOUND' 72 + | 'SIGNING_FAILED' 73 + | 'INVALID_SIGNATURE' 74 + | 'KEYCHAIN_ERROR'; 75 + message?: string; 76 + }; 77 + 78 + // ── get_or_create_device_key ───────────────────────────────────────────────── 79 + 80 + /** 81 + * Get or create the device's SE-backed (or simulator-fallback) P-256 keypair. 82 + * 83 + * Idempotent — returns the same key on every call for a given device. 84 + * On failure, the Promise rejects with a `DeviceKeyError`. 85 + */ 86 + export const getOrCreateDeviceKey = (): Promise<DevicePublicKey> => 87 + invoke('get_or_create_device_key'); 88 + 89 + // ── sign_with_device_key ───────────────────────────────────────────────────── 90 + 91 + /** 92 + * Sign arbitrary bytes using the device's SE-backed (or simulator-fallback) P-256 key. 93 + * 94 + * Returns the raw 64-byte ECDSA r||s signature as a Uint8Array. 95 + * 96 + * IMPORTANT: `data` is converted to `number[]` before passing to Tauri's IPC 97 + * because Tauri v2's JSON deserializer cannot accept a `Uint8Array` nested inside 98 + * an object property — it must be a plain number array. See tauri#10336. 99 + * 100 + * On failure, the Promise rejects with a `DeviceKeyError` (code: KEY_NOT_FOUND 101 + * if `getOrCreateDeviceKey` has never been called for this device). 102 + */ 103 + export const signWithDeviceKey = (data: Uint8Array): Promise<Uint8Array> => 104 + (invoke('sign_with_device_key', { data: Array.from(data) }) as Promise<number[]>).then( 105 + (bytes) => new Uint8Array(bytes), 106 + );