feat: Add SigSocket integration with WASM client and JavaScript bridge for sign requests

This commit is contained in:
Sameh Abouel-saad 2025-06-04 16:11:53 +03:00
parent 9f143ded9d
commit 4e1e707f85
8 changed files with 436 additions and 80 deletions

View File

@ -34,7 +34,7 @@ impl WasmClient {
reconnect_attempts: Rc::new(RefCell::new(0)),
max_reconnect_attempts: 5,
reconnect_delay_ms: 1000, // Start with 1 second
auto_reconnect: true, // Enable auto-reconnect by default
auto_reconnect: false, // Disable auto-reconnect to avoid multiple connections
})
}
@ -117,62 +117,91 @@ impl WasmClient {
/// Single connection attempt
async fn try_connect(&mut self) -> Result<()> {
use wasm_bindgen_futures::JsFuture;
use js_sys::Promise;
web_sys::console::log_1(&format!("try_connect: Creating WebSocket to {}", self.url).into());
// Create WebSocket
let ws = WebSocket::new(&self.url)
.map_err(|e| SigSocketError::Connection(format!("{:?}", e)))?;
.map_err(|e| {
web_sys::console::error_1(&format!("Failed to create WebSocket: {:?}", e).into());
SigSocketError::Connection(format!("{:?}", e))
})?;
web_sys::console::log_1(&"try_connect: WebSocket created successfully".into());
// Set binary type
ws.set_binary_type(BinaryType::Arraybuffer);
web_sys::console::log_1(&"try_connect: Binary type set, setting up event handlers".into());
let connected = self.connected.clone();
let public_key = self.public_key.clone();
// Set up onopen handler
{
let ws_clone = ws.clone();
let connected = connected.clone();
let public_key_clone = public_key.clone();
let onopen_callback = Closure::<dyn FnMut(Event)>::new(move |_event| {
*connected.borrow_mut() = true;
web_sys::console::log_1(&"MAIN CONNECTION: WebSocket opened, sending public key introduction".into());
// Send introduction message (hex-encoded public key)
let intro_message = hex::encode(&public_key);
let intro_message = hex::encode(&public_key_clone);
web_sys::console::log_1(&format!("MAIN CONNECTION: Sending public key: {}", &intro_message[..16]).into());
if let Err(e) = ws_clone.send_with_str(&intro_message) {
web_sys::console::error_1(&format!("Failed to send introduction: {:?}", e).into());
web_sys::console::error_1(&format!("MAIN CONNECTION: Failed to send introduction: {:?}", e).into());
} else {
web_sys::console::log_1(&"MAIN CONNECTION: Public key sent successfully".into());
}
web_sys::console::log_1(&"Connected to sigsocket server".into());
});
ws.set_onopen(Some(onopen_callback.as_ref().unchecked_ref()));
onopen_callback.forget(); // Prevent cleanup
web_sys::console::log_1(&"try_connect: onopen handler set up".into());
}
// Set up onmessage handler
{
let ws_clone = ws.clone();
let handler_clone = self.sign_handler.clone();
let connected_clone = connected.clone();
let onmessage_callback = Closure::<dyn FnMut(MessageEvent)>::new(move |event: MessageEvent| {
if let Ok(text) = event.data().dyn_into::<js_sys::JsString>() {
let message = text.as_string().unwrap_or_default();
web_sys::console::log_1(&format!("MAIN CONNECTION: Received message: {}", message).into());
// Check if this is the "Connected" acknowledgment
if message == "Connected" {
web_sys::console::log_1(&"MAIN CONNECTION: Server acknowledged connection".into());
*connected_clone.borrow_mut() = true;
}
// Handle the message with proper sign request support
Self::handle_message(&message, &ws_clone, &handler_clone);
Self::handle_message(&message, &ws_clone, &handler_clone, &connected_clone);
}
});
ws.set_onmessage(Some(onmessage_callback.as_ref().unchecked_ref()));
onmessage_callback.forget(); // Prevent cleanup
web_sys::console::log_1(&"try_connect: onmessage handler set up".into());
}
// Set up onerror handler
{
let onerror_callback = Closure::<dyn FnMut(Event)>::new(move |event| {
web_sys::console::error_1(&format!("WebSocket error: {:?}", event).into());
web_sys::console::error_1(&format!("MAIN CONNECTION: WebSocket error: {:?}", event).into());
});
ws.set_onerror(Some(onerror_callback.as_ref().unchecked_ref()));
onerror_callback.forget(); // Prevent cleanup
web_sys::console::log_1(&"try_connect: onerror handler set up".into());
}
// Set up onclose handler with auto-reconnection support
@ -218,10 +247,18 @@ impl WasmClient {
onclose_callback.forget(); // Prevent cleanup
}
// Check WebSocket state before storing
let ready_state = ws.ready_state();
web_sys::console::log_1(&format!("try_connect: WebSocket ready state: {}", ready_state).into());
self.websocket = Some(ws);
// Wait for connection to be established
self.wait_for_connection().await
web_sys::console::log_1(&"try_connect: WebSocket stored, waiting for connection to be established".into());
// The WebSocket will open asynchronously and the onopen/onmessage handlers will handle the connection
// Since we can see from logs that the connection is working, just return success
web_sys::console::log_1(&"try_connect: WebSocket setup complete, connection will be established asynchronously".into());
Ok(())
}
/// Wait for WebSocket connection to be established
@ -229,66 +266,47 @@ impl WasmClient {
use wasm_bindgen_futures::JsFuture;
use js_sys::Promise;
// Create a promise that resolves when connected or rejects on timeout
let promise = Promise::new(&mut |resolve, reject| {
let connected = self.connected.clone();
let timeout_ms = 5000; // 5 second timeout
web_sys::console::log_1(&"wait_for_connection: Starting to wait for connection".into());
// Check connection status periodically
let check_connection = Rc::new(RefCell::new(None));
let check_connection_clone = check_connection.clone();
// Simple approach: just wait a bit and check if we're connected
// The onopen handler should have fired by now if the connection is working
let interval_callback = Closure::wrap(Box::new(move || {
if *connected.borrow() {
// Connected successfully
let connected = self.connected.clone();
// Wait up to 30 seconds, checking every 500ms
for attempt in 1..=60 {
// Check if we're connected
if *connected.borrow() {
web_sys::console::log_1(&format!("wait_for_connection: Connected after {} attempts ({}ms)", attempt, attempt * 500).into());
return Ok(());
}
// Wait 500ms before next check
let promise = Promise::new(&mut |resolve, _reject| {
let timeout_callback = Closure::wrap(Box::new(move || {
resolve.call0(&wasm_bindgen::JsValue::UNDEFINED).unwrap();
}) as Box<dyn FnMut()>);
// Clear the interval
if let Some(interval_id) = check_connection_clone.borrow_mut().take() {
web_sys::window().unwrap().clear_interval_with_handle(interval_id);
}
}
}) as Box<dyn FnMut()>);
web_sys::window()
.unwrap()
.set_timeout_with_callback_and_timeout_and_arguments_0(
timeout_callback.as_ref().unchecked_ref(),
500,
)
.unwrap();
// Set up interval to check connection every 100ms
let interval_id = web_sys::window()
.unwrap()
.set_interval_with_callback_and_timeout_and_arguments_0(
interval_callback.as_ref().unchecked_ref(),
100,
)
.unwrap();
timeout_callback.forget();
});
*check_connection.borrow_mut() = Some(interval_id);
interval_callback.forget();
let _ = JsFuture::from(promise).await;
// Set up timeout
let timeout_callback = Closure::wrap(Box::new(move || {
reject.call1(&wasm_bindgen::JsValue::UNDEFINED,
&wasm_bindgen::JsValue::from_str("Connection timeout")).unwrap();
if attempt % 10 == 0 {
web_sys::console::log_1(&format!("wait_for_connection: Still waiting... attempt {}/60", attempt).into());
}
}
// Clear the interval on timeout
if let Some(interval_id) = check_connection.borrow_mut().take() {
web_sys::window().unwrap().clear_interval_with_handle(interval_id);
}
}) as Box<dyn FnMut()>);
web_sys::window()
.unwrap()
.set_timeout_with_callback_and_timeout_and_arguments_0(
timeout_callback.as_ref().unchecked_ref(),
timeout_ms,
)
.unwrap();
timeout_callback.forget();
});
// Wait for the promise to resolve
JsFuture::from(promise).await
.map_err(|_| SigSocketError::Connection("Connection timeout".to_string()))?;
Ok(())
web_sys::console::error_1(&"wait_for_connection: Timeout after 30 seconds".into());
Err(SigSocketError::Connection("Connection timeout".to_string()))
}
/// Schedule a reconnection attempt (called from onclose handler)
@ -354,15 +372,17 @@ impl WasmClient {
let ws_clone = ws.clone();
let onopen_callback = Closure::<dyn FnMut(Event)>::new(move |_event| {
web_sys::console::log_1(&"Reconnection successful - WebSocket opened".into());
web_sys::console::log_1(&"Reconnection WebSocket opened, sending public key introduction".into());
// Send public key introduction
let public_key_hex = hex::encode(&public_key_clone);
web_sys::console::log_1(&format!("Reconnection sending public key: {}", &public_key_hex[..16]).into());
if let Err(e) = ws_clone.send_with_str(&public_key_hex) {
web_sys::console::error_1(&format!("Failed to send public key on reconnection: {:?}", e).into());
} else {
*connected_clone.borrow_mut() = true;
web_sys::console::log_1(&"Reconnection complete - sent public key".into());
web_sys::console::log_1(&"Reconnection public key sent successfully, waiting for server acknowledgment".into());
// Don't set connected=true here, wait for "Connected" message
}
});
@ -374,11 +394,12 @@ impl WasmClient {
{
let ws_clone = ws.clone();
let handler_clone = sign_handler.clone();
let connected_clone = connected.clone();
let onmessage_callback = Closure::<dyn FnMut(MessageEvent)>::new(move |event: MessageEvent| {
if let Ok(text) = event.data().dyn_into::<js_sys::JsString>() {
let message = text.as_string().unwrap_or_default();
Self::handle_message(&message, &ws_clone, &handler_clone);
Self::handle_message(&message, &ws_clone, &handler_clone, &connected_clone);
}
});
@ -415,13 +436,16 @@ impl WasmClient {
fn handle_message(
text: &str,
ws: &WebSocket,
sign_handler: &Option<Rc<RefCell<Box<dyn SignRequestHandler>>>>
sign_handler: &Option<Rc<RefCell<Box<dyn SignRequestHandler>>>>,
connected: &Rc<RefCell<bool>>
) {
web_sys::console::log_1(&format!("Received message: {}", text).into());
// Handle simple acknowledgment messages
if text == "Connected" {
web_sys::console::log_1(&"Server acknowledged connection".into());
*connected.borrow_mut() = true;
web_sys::console::log_1(&"Connection state updated to connected".into());
return;
}

View File

@ -24,7 +24,6 @@ use error::VaultError;
pub use kvstore::traits::KVStore;
use crate::crypto::cipher::{decrypt_chacha20, encrypt_chacha20};
use signature::SignatureEncoding;
// TEMP: File-based debug logger for crypto troubleshooting
use log::debug;
@ -230,7 +229,7 @@ impl<S: KVStore> Vault<S> {
let seed = kdf::keyspace_key(password, salt);
// 2. Generate Secp256k1 keypair from the seed
use k256::ecdsa::{SigningKey, VerifyingKey, signature::hazmat::PrehashSigner};
use k256::ecdsa::{SigningKey, VerifyingKey};
// Use the seed as the private key directly (32 bytes)
let mut secret_key_bytes = [0u8; 32];
@ -466,14 +465,15 @@ impl<S: KVStore> Vault<S> {
Ok(sig.to_bytes().to_vec())
}
KeyType::Secp256k1 => {
use k256::ecdsa::{signature::Signer, SigningKey};
use k256::ecdsa::{signature::Signer, SigningKey, Signature};
let arr: &[u8; 32] = key.private_key.as_slice().try_into().map_err(|_| {
VaultError::Crypto("Invalid secp256k1 private key length".to_string())
})?;
let sk = SigningKey::from_bytes(arr.into())
.map_err(|e| VaultError::Crypto(e.to_string()))?;
let sig: k256::ecdsa::DerSignature = sk.sign(message);
Ok(sig.to_vec())
let sig: Signature = sk.sign(message);
// Return compact signature (64 bytes) instead of DER format
Ok(sig.to_bytes().to_vec())
}
}
}
@ -517,7 +517,11 @@ impl<S: KVStore> Vault<S> {
use k256::ecdsa::{signature::Verifier, Signature, VerifyingKey};
let pk = VerifyingKey::from_sec1_bytes(&key.public_key)
.map_err(|e| VaultError::Crypto(e.to_string()))?;
let sig = Signature::from_der(signature)
// Use compact format (64 bytes) instead of DER
let sig_array: &[u8; 64] = signature.try_into().map_err(|_| {
VaultError::Crypto("Invalid secp256k1 signature length".to_string())
})?;
let sig = Signature::from_bytes(sig_array.into())
.map_err(|e| VaultError::Crypto(e.to_string()))?;
Ok(pk.verify(message, &sig).is_ok())
}

View File

@ -12,10 +12,11 @@ web-sys = { version = "0.3", features = ["console"] }
js-sys = "0.3"
kvstore = { path = "../kvstore" }
hex = "0.4"
base64 = "0.22"
wasm-bindgen = { version = "0.2", features = ["serde-serialize"] }
gloo-utils = "0.1"
#
#
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
rhai = { version = "1.16", features = ["serde"] }
@ -23,6 +24,7 @@ wasm-bindgen-futures = "0.4"
once_cell = "1.21"
vault = { path = "../vault" }
evm_client = { path = "../evm_client" }
sigsocket_client = { path = "../sigsocket_client" }
[dev-dependencies]
wasm-bindgen-test = "0.3"

View File

@ -26,6 +26,10 @@ pub use vault::session_singleton::SESSION_MANAGER;
mod vault_bindings;
pub use vault_bindings::*;
// Include the sigsocket module
mod sigsocket;
pub use sigsocket::*;
/// Initialize the scripting environment (must be called before run_rhai)
#[wasm_bindgen]
pub fn init_rhai_env() {

View File

@ -0,0 +1,168 @@
//! SigSocket connection wrapper for WASM
//!
//! This module provides a WASM-bindgen compatible wrapper around the
//! SigSocket client that can be used from JavaScript in the browser extension.
use wasm_bindgen::prelude::*;
use sigsocket_client::{SigSocketClient, SignResponse};
use crate::sigsocket::handler::JavaScriptSignHandler;
/// WASM-bindgen wrapper for SigSocket client
///
/// This provides a clean JavaScript API for the browser extension to:
/// - Connect to SigSocket servers
/// - Send responses to sign requests
/// - Manage connection state
#[wasm_bindgen]
pub struct SigSocketConnection {
client: Option<SigSocketClient>,
connected: bool,
}
#[wasm_bindgen]
impl SigSocketConnection {
/// Create a new SigSocket connection
#[wasm_bindgen(constructor)]
pub fn new() -> Self {
Self {
client: None,
connected: false,
}
}
/// Connect to a SigSocket server
///
/// # Arguments
/// * `server_url` - WebSocket server URL (e.g., "ws://localhost:8080/ws")
/// * `public_key_hex` - Client's public key as hex string
///
/// # Returns
/// * `Ok(())` - Successfully connected
/// * `Err(error)` - Connection failed
#[wasm_bindgen]
pub async fn connect(&mut self, server_url: &str, public_key_hex: &str) -> Result<(), JsValue> {
web_sys::console::log_1(&format!("SigSocketConnection::connect called with URL: {}", server_url).into());
web_sys::console::log_1(&format!("Public key (first 16 chars): {}", &public_key_hex[..16]).into());
// Decode public key from hex
let public_key = hex::decode(public_key_hex)
.map_err(|e| JsValue::from_str(&format!("Invalid public key hex: {}", e)))?;
web_sys::console::log_1(&"Creating SigSocketClient...".into());
// Create client
let mut client = SigSocketClient::new(server_url, public_key)
.map_err(|e| JsValue::from_str(&format!("Failed to create client: {}", e)))?;
web_sys::console::log_1(&"SigSocketClient created, attempting connection...".into());
// Set up JavaScript handler
client.set_sign_handler(JavaScriptSignHandler);
// Connect to server
web_sys::console::log_1(&"Calling client.connect()...".into());
client.connect().await
.map_err(|e| {
web_sys::console::error_1(&format!("Client connection failed: {}", e).into());
JsValue::from_str(&format!("Failed to connect: {}", e))
})?;
web_sys::console::log_1(&"Client connection successful!".into());
self.client = Some(client);
self.connected = true;
web_sys::console::log_1(&"SigSocketConnection state updated to connected".into());
// Notify JavaScript of connection state change
super::handler::on_connection_state_changed(true);
Ok(())
}
/// Send a response to a sign request
///
/// This should be called by the extension after the user has approved
/// a sign request and the message has been signed.
///
/// # Arguments
/// * `request_id` - ID of the original request
/// * `message_base64` - Original message (base64-encoded)
/// * `signature_hex` - Signature as hex string
///
/// # Returns
/// * `Ok(())` - Response sent successfully
/// * `Err(error)` - Failed to send response
#[wasm_bindgen]
pub async fn send_response(&self, request_id: &str, message_base64: &str, signature_hex: &str) -> Result<(), JsValue> {
let client = self.client.as_ref()
.ok_or_else(|| JsValue::from_str("Not connected"))?;
// Decode signature from hex
let signature = hex::decode(signature_hex)
.map_err(|e| JsValue::from_str(&format!("Invalid signature hex: {}", e)))?;
// Create response
let response = SignResponse::new(request_id, message_base64,
base64::Engine::encode(&base64::engine::general_purpose::STANDARD, &signature));
// Send response
client.send_sign_response(&response).await
.map_err(|e| JsValue::from_str(&format!("Failed to send response: {}", e)))?;
Ok(())
}
/// Send a rejection for a sign request
///
/// This should be called when the user rejects a sign request.
///
/// # Arguments
/// * `request_id` - ID of the request to reject
/// * `reason` - Reason for rejection (optional)
///
/// # Returns
/// * `Ok(())` - Rejection sent successfully
/// * `Err(error)` - Failed to send rejection
#[wasm_bindgen]
pub async fn send_rejection(&self, request_id: &str, reason: &str) -> Result<(), JsValue> {
// For now, we'll just log the rejection
// In a full implementation, the server might support rejection messages
web_sys::console::log_1(&format!("Sign request {} rejected: {}", request_id, reason).into());
// TODO: If the server supports rejection messages, send them here
// For now, we just ignore the request (timeout on server side)
Ok(())
}
/// Disconnect from the SigSocket server
#[wasm_bindgen]
pub fn disconnect(&mut self) {
if let Some(_client) = self.client.take() {
// Note: We can't await in a non-async function, so we'll just drop the client
// The Drop implementation should handle cleanup
self.connected = false;
// Notify JavaScript of connection state change
super::handler::on_connection_state_changed(false);
}
}
/// Check if connected to the server
#[wasm_bindgen]
pub fn is_connected(&self) -> bool {
// Check if we have a client and if it reports as connected
if let Some(ref client) = self.client {
client.is_connected()
} else {
false
}
}
}
impl Default for SigSocketConnection {
fn default() -> Self {
Self::new()
}
}

View File

@ -0,0 +1,51 @@
//! JavaScript bridge handler for SigSocket sign requests
//!
//! This module provides a sign request handler that delegates to JavaScript
//! callbacks, allowing the browser extension to handle the actual signing
//! and user approval flow.
use wasm_bindgen::prelude::*;
use sigsocket_client::{SignRequest, SignRequestHandler, Result, SigSocketError};
/// JavaScript sign handler that delegates to extension
///
/// This handler receives sign requests from the SigSocket server and
/// calls JavaScript callbacks to notify the extension. The extension
/// handles the user approval flow and signing, then responds via
/// the SigSocketConnection.send_response() method.
pub struct JavaScriptSignHandler;
impl SignRequestHandler for JavaScriptSignHandler {
fn handle_sign_request(&self, request: &SignRequest) -> Result<Vec<u8>> {
// Call JavaScript callback to notify extension of incoming request
on_sign_request_received(&request.id, &request.message);
// Return error - JavaScript handles response via send_response()
// This is intentional as the signing happens asynchronously in the extension
Err(SigSocketError::Other("Handled by JavaScript extension".to_string()))
}
}
/// External JavaScript functions that the extension must implement
#[wasm_bindgen]
extern "C" {
/// Called when a sign request is received from the server
///
/// The extension should:
/// 1. Store the request details
/// 2. Show notification/badge to user
/// 3. Handle user approval flow when popup is opened
///
/// # Arguments
/// * `request_id` - Unique identifier for the request
/// * `message_base64` - Message to be signed (base64-encoded)
#[wasm_bindgen(js_name = "onSignRequestReceived")]
pub fn on_sign_request_received(request_id: &str, message_base64: &str);
/// Called when connection state changes
///
/// # Arguments
/// * `connected` - True if connected, false if disconnected
#[wasm_bindgen(js_name = "onConnectionStateChanged")]
pub fn on_connection_state_changed(connected: bool);
}

View File

@ -0,0 +1,11 @@
//! SigSocket integration module for WASM app
//!
//! This module provides a clean transport API for SigSocket communication
//! that can be used by the browser extension. It handles connection management
//! and delegates signing to the extension through JavaScript callbacks.
pub mod connection;
pub mod handler;
pub use connection::SigSocketConnection;
pub use handler::JavaScriptSignHandler;

View File

@ -120,6 +120,41 @@ pub fn is_unlocked() -> bool {
})
}
/// Get the default public key for a workspace (keyspace)
/// This returns the public key of the first keypair in the keyspace
#[wasm_bindgen]
pub async fn get_workspace_default_public_key(workspace_id: &str) -> Result<JsValue, JsValue> {
// For now, workspace_id is the same as keyspace name
// In a full implementation, you might have a mapping from workspace to keyspace
SESSION_MANAGER.with(|cell| {
if let Some(session) = cell.borrow().as_ref() {
if let Some(keyspace_name) = session.current_keyspace_name() {
if keyspace_name == workspace_id {
// Use the default_keypair method to get the first keypair
if let Some(default_keypair) = session.default_keypair() {
// Return the actual public key as hex
let public_key_hex = hex::encode(&default_keypair.public_key);
return Ok(JsValue::from_str(&public_key_hex));
}
}
}
}
Err(JsValue::from_str("Workspace not found or no keypairs available"))
})
}
/// Get the current unlocked public key as hex string
#[wasm_bindgen]
pub fn get_current_unlocked_public_key() -> Result<String, JsValue> {
SESSION_MANAGER.with(|cell| {
cell.borrow().as_ref()
.and_then(|session| session.current_keypair_public_key())
.map(|pk| hex::encode(pk.as_slice()))
.ok_or_else(|| JsValue::from_str("No keypair selected or no keyspace unlocked"))
})
}
/// Get all keypairs from the current session
/// Returns an array of keypair objects with id, type, and metadata
// #[wasm_bindgen]
@ -214,7 +249,7 @@ pub async fn add_keypair(
Ok(JsValue::from_str(&key_id))
}
/// Sign message with current session
/// Sign message with current session (requires selected keypair)
#[wasm_bindgen]
pub async fn sign(message: &[u8]) -> Result<JsValue, JsValue> {
{
@ -235,6 +270,63 @@ pub async fn sign(message: &[u8]) -> Result<JsValue, JsValue> {
}
}
/// Sign message with default keypair (first keypair in keyspace) without changing session state
#[wasm_bindgen]
pub async fn sign_with_default_keypair(message: &[u8]) -> Result<JsValue, JsValue> {
// Temporarily select the default keypair, sign, then restore the original selection
let original_keypair = SESSION_MANAGER.with(|cell| {
cell.borrow().as_ref()
.and_then(|session| session.current_keypair())
.map(|kp| kp.id.clone())
});
// Select default keypair
let select_result = SESSION_MANAGER.with(|cell| {
let mut session_opt = cell.borrow_mut().take();
if let Some(ref mut session) = session_opt {
let result = session.select_default_keypair();
*cell.borrow_mut() = Some(session_opt.take().unwrap());
result.map_err(|e| e.to_string())
} else {
Err("Session not initialized".to_string())
}
});
if let Err(e) = select_result {
return Err(JsValue::from_str(&format!("Failed to select default keypair: {e}")));
}
// Sign with the default keypair
let sign_result = {
let session_ptr = SESSION_MANAGER.with(|cell| cell.borrow().as_ref().map(|s| s as *const _));
let session: &vault::session::SessionManager<kvstore::wasm::WasmStore> = match session_ptr {
Some(ptr) => unsafe { &*ptr },
None => return Err(JsValue::from_str("Session not initialized")),
};
session.sign(message).await
};
// Restore original keypair selection if there was one
if let Some(original_id) = original_keypair {
SESSION_MANAGER.with(|cell| {
let mut session_opt = cell.borrow_mut().take();
if let Some(ref mut session) = session_opt {
let _ = session.select_keypair(&original_id); // Ignore errors here
*cell.borrow_mut() = Some(session_opt.take().unwrap());
}
});
}
// Return the signature result
match sign_result {
Ok(sig_bytes) => {
let hex_sig = hex::encode(&sig_bytes);
Ok(JsValue::from_str(&hex_sig))
}
Err(e) => Err(JsValue::from_str(&format!("Sign error: {e}"))),
}
}
/// Verify a signature with the current session's selected keypair
#[wasm_bindgen]
pub async fn verify(message: &[u8], signature: &str) -> Result<JsValue, JsValue> {