From 95af4aece30145cc9b22b07c8e21b8eb29182f38 Mon Sep 17 00:00:00 2001 From: 448 OG Date: Tue, 9 Sep 2025 12:04:29 +0300 Subject: [PATCH] Finish solana:signMessage Handle sign message request of a single message that can be later expanded to multiple messages. --- wallet-extension/extension/js/content.js | 58 +++++++++++++--- .../src/impl_solana/interface/mod.rs | 1 + .../src/impl_solana/interface/sign_in.rs | 60 ---------------- .../src/impl_solana/interface/sign_message.rs | 69 +++++++++++++++++++ wallet-extension/src/impl_solana/wallet.rs | 9 +++ wallet-extension/src/message_handler.rs | 3 + 6 files changed, 129 insertions(+), 71 deletions(-) create mode 100644 wallet-extension/src/impl_solana/interface/sign_message.rs diff --git a/wallet-extension/extension/js/content.js b/wallet-extension/extension/js/content.js index 3a037ff..e333fd9 100644 --- a/wallet-extension/extension/js/content.js +++ b/wallet-extension/extension/js/content.js @@ -13,6 +13,9 @@ const RELAY_SOLANA_SIGN_IN = "relay:solana:signIn"; const SOLANA_SIGN_IN = "solana:signIn"; + const RELAY_SIGN_MESSAGE = "relay:solana:signMessage"; + const SOLANA_SIGN_MESSAGE = "solana:signMessage"; + function injectPageWallet(walletInfo) { const STANDARD_CONNECT = "standard:connect"; const RELAY_STANDARD_CONNECT = "relay:standard:connect"; @@ -20,6 +23,9 @@ const RELAY_SOLANA_SIGN_IN = "relay:solana:signIn"; const SOLANA_SIGN_IN = "solana:signIn"; + const RELAY_SIGN_MESSAGE = "relay:solana:signMessage"; + const SOLANA_SIGN_MESSAGE = "solana:signMessage"; + const WALLET_REGISTER_EVENT = "wallet-standard:register-wallet"; const APP_READY_EVENT = "wallet-standard:app-ready"; @@ -167,13 +173,26 @@ return [{ signedTransaction: new Uint8Array([4, 5, 6]) }]; }; #signMessage = async (...inputs) => { - console.log("SIGN MSG", ...inputs); - return [ - { - signedMessage: new Uint8Array([7, 8, 9]), - signature: new Uint8Array([10, 11, 12]), - }, - ]; + const result = await new Promise((resolve, reject) => { + const listener = (event) => { + if (event.source !== window) return; + if (event.data.type === RELAY_SIGN_MESSAGE) { + window.removeEventListener("message", listener); + + if (event.data.failure) reject(new Error(event.data.failure)); + else resolve(event.data.success); + } + }; + window.addEventListener("message", listener); + + // Send request → content.js + window.postMessage( + { type: SOLANA_SIGN_MESSAGE, requestData: inputs[0], text: "" }, + "*" + ); + }); + + return result; }; #signIn = async (...inputs) => { const result = await new Promise((resolve, reject) => { @@ -226,7 +245,6 @@ extension.runtime.sendMessage( { resource: event.data.type, data: window.location.origin }, (response) => { - console.log("Content script got response from background:", response); // Relay back under RELAY_STANDARD_CONNECT window.postMessage( { @@ -242,14 +260,11 @@ }); window.addEventListener("message", (event) => { - console.log("SignIn event:", event); - if (event.source !== window) return; if (event.data.type === SOLANA_SIGN_IN) { extension.runtime.sendMessage( { resource: event.data.type, data: event.data }, (response) => { - console.log("Content script got response from background:", response); // Relay back under RELAY_STANDARD_CONNECT window.postMessage( { @@ -263,4 +278,25 @@ ); } }); + + window.addEventListener("message", (event) => { + if (event.source !== window) return; + if (event.data.type === SOLANA_SIGN_MESSAGE) { + extension.runtime.sendMessage( + { resource: event.data.type, data: event.data }, + (response) => { + console.log("Content script got response from background:", response); + // Relay back under RELAY_STANDARD_CONNECT + window.postMessage( + { + type: RELAY_SIGN_MESSAGE, + success: response.success, + failure: response.failure, + }, + "*" + ); + } + ); + } + }); })(); diff --git a/wallet-extension/src/impl_solana/interface/mod.rs b/wallet-extension/src/impl_solana/interface/mod.rs index 654fc13..7233d3d 100644 --- a/wallet-extension/src/impl_solana/interface/mod.rs +++ b/wallet-extension/src/impl_solana/interface/mod.rs @@ -1,2 +1,3 @@ mod sign_in; +mod sign_message; mod standard_connect; diff --git a/wallet-extension/src/impl_solana/interface/sign_in.rs b/wallet-extension/src/impl_solana/interface/sign_in.rs index db8363b..c89fc79 100644 --- a/wallet-extension/src/impl_solana/interface/sign_in.rs +++ b/wallet-extension/src/impl_solana/interface/sign_in.rs @@ -335,63 +335,3 @@ impl<'wa> SignInInputParser<'wa> { Ok(self) } } - -/* -#[derive(Debug, Clone, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct SignInInput<'wa> { - /// Optional EIP-4361 domain requesting the sign-in. - /// If not provided, the wallet must determine the domain to include in the message. - domain: Option>, - /// Optional Solana Base58 address performing the sign-in. - /// The address is case-sensitive. - /// If not provided, the wallet must determine the Address to include in the message. - address: Option>, - /// Optional EIP-4361 Statement. - /// The statement is a human readable string and should not have new-line characters (\n). - /// If not provided, the wallet does not include Statement in the message. - statement: Option>, - /// Optional EIP-4361 URI. - /// The URL that is requesting the sign-in. - /// If not provided, the wallet does not include URI in the message. - uri: Option>, - /// Optional EIP-4361 version. - /// If not provided, the wallet does not include Version in the message. - version: Option>, - /// Optional EIP-4361 Chain ID. - /// The chainId can be one of the following: - /// mainnet, testnet, devnet, localnet, solana:mainnet, solana:testnet, solana:devnet. - /// If not provided, the wallet does not include Chain ID in the message. - chain_id: Option>, - /// Optional EIP-4361 Nonce. - /// It should be an alphanumeric string containing a minimum of 8 characters. - /// If not provided, the wallet does not include Nonce in the message. - nonce: Option>, - /// Optional ISO 8601 datetime string. - /// This represents the time at which the sign-in request was issued to the wallet. - /// Note: For Phantom, issuedAt has a threshold and it should be - /// within +- 10 minutes from the timestamp at which verification is taking place. - /// If not provided, the wallet does not include Issued At in the message. - issued_at: Option, - /// Optional ISO 8601 datetime string. - /// This represents the time at which the sign-in request should expire. - /// If not provided, the wallet does not include Expiration Time in the message. - expiration_time: Option, - /// Optional ISO 8601 datetime string. - /// This represents the time at which the sign-in request becomes valid. - /// If not provided, the wallet does not include Not Before in the message. - not_before: Option, - /// Optional EIP-4361 Request ID. - /// In addition to using nonce to avoid replay attacks, - /// dapps can also choose to include a unique signature in the requestId . - /// Once the wallet returns the signed message, - /// dapps can then verify this signature against the state to add an additional, - /// strong layer of security. If not provided, the wallet does not include Request ID in the message. - request_id: Option>, - /// Optional EIP-4361 Resources. - /// Usually a list of references in the form of URIs that the - /// dapp wants the user to be aware of. - /// These URIs should be separated by \n-, ie, - /// URIs in new lines starting with the character -. - /// If not provided, the wallet does not include Resources in the message. - resources: Cow<'wa, [&'wa str]>, -} */ diff --git a/wallet-extension/src/impl_solana/interface/sign_message.rs b/wallet-extension/src/impl_solana/interface/sign_message.rs new file mode 100644 index 0000000..33a1481 --- /dev/null +++ b/wallet-extension/src/impl_solana/interface/sign_message.rs @@ -0,0 +1,69 @@ +use wasm_bindgen::{JsCast, JsValue}; +use web_sys::js_sys::{self, Array, Uint8Array}; + +use crate::{ + App, AtollWalletError, AtollWalletResult, Reflection, SolanaConstants, app_console_log, +}; + +impl App { + pub fn solana_sign_message(&mut self, data: JsValue) -> AtollWalletResult { + app_console_log(SolanaConstants::SIGN_MESSAGE, &data); + let data = Reflection::new_object_from_js_value(data)?; + + let data = data.get_object( + "requestData", + AtollWalletError::JsCast("`requestData` not found in `message` object".to_string()), + )?; + + let data = Reflection::new_object_from_js_value(data)?; + let account_js_value = data.get_object( + "account", + AtollWalletError::JsCast( + "`account` object was not found in the `requestData` for `solana:signMessage`" + .to_string(), + ), + )?; + let account_object = Reflection::new_object_from_js_value(account_js_value)?; + let public_key: [u8;32] = account_object.get_object( + "publicKey", + AtollWalletError::JsCast( + "`publicKey` was not found in the `requestData.accounts` for `solana:signMessage`" + .to_string(), + ), + )?.dyn_into::().or( + Err(AtollWalletError::JsCast("`publicKey` in `requestData` for `solana:signMessage` not a byte array.".to_string())) + )?.to_vec().try_into().or(Err(AtollWalletError::Input("`publicKey` in `requestData` for `solana:signMessage` not an byte array of 32 in length.".to_string())))?; + let message_js_value = data.get_object( + "message", + AtollWalletError::JsCast( + "`message` was not found in the `requestData` for `solana:signMessage`".to_string(), + ), + )?; + + let message_bytes = message_js_value + .dyn_into::() + .or(Err(AtollWalletError::JsCast( + "`message` in `requestData` for `solana:signMessage` not a byte array.".to_string(), + )))? + .to_vec(); + + let signature = self + .active_keypair()? + .sign_message(&public_key, &message_bytes); + + let sign_in_output = Reflection::new_object(); + + let signed_message = Uint8Array::new_from_slice(message_bytes.as_slice()); + sign_in_output.set_object_secure("signedMessage", &signed_message); + + let signature = Uint8Array::new_from_slice(&signature); + sign_in_output.set_object_secure("signature", &signature); + + sign_in_output.set_object_secure("signatureType", &"ed25519".into()); + + let output_array = Array::new(); + output_array.push(&sign_in_output.take()); + + Ok(output_array.into()) + } +} diff --git a/wallet-extension/src/impl_solana/wallet.rs b/wallet-extension/src/impl_solana/wallet.rs index 2b9f8dd..1ebdc6e 100644 --- a/wallet-extension/src/impl_solana/wallet.rs +++ b/wallet-extension/src/impl_solana/wallet.rs @@ -86,9 +86,18 @@ impl<'wa> SolanaAccountKeypair { pub fn sign_in(&'wa mut self, formatted_input: &str) -> (SolanaWalletAccount<'wa>, [u8; 64]) { let signature = self.keypair.sign_message(formatted_input.as_bytes()); + // TODO add to active dapps and add dapp icon too + (self.get_wallet_account(), *signature.as_array()) } + // TODO type checks to see if a dapp is currently authorized to perform an operation + pub fn sign_message(&mut self, public_key: &[u8; 32], message: &[u8]) -> [u8; 64] { + let signature = self.keypair.sign_message(message); + + *signature.as_array() + } + pub fn get_wallet_account(&'wa self) -> SolanaWalletAccount<'wa> { let public_key = self.pubkey().to_bytes(); diff --git a/wallet-extension/src/message_handler.rs b/wallet-extension/src/message_handler.rs index 61b3aaa..453d5ab 100644 --- a/wallet-extension/src/message_handler.rs +++ b/wallet-extension/src/message_handler.rs @@ -92,6 +92,7 @@ fn match_message(message: JsValue, app: AppManager) -> AtollWalletResult app.borrow_mut().standard_connect(&data), ExtensionMessage::SolanaSignIn => app.borrow_mut().solana_sign_in(data), + ExtensionMessage::SolanaSignMessage => app.borrow_mut().solana_sign_message(data), } } @@ -99,6 +100,7 @@ fn match_message(message: JsValue, app: AppManager) -> AtollWalletResult for ExtensionMessage { @@ -112,6 +114,7 @@ impl TryFrom<&JsValue> for ExtensionMessage { let matched = match parsed_js_value.as_str() { SolanaConstants::STANDARD_CONNECT => Self::StandardConnect, SolanaConstants::SIGN_IN => Self::SolanaSignIn, + SolanaConstants::SIGN_MESSAGE => Self::SolanaSignMessage, _ => { return Err(AtollWalletError::UnsupportedExtensionMessage( parsed_js_value,