From 23dbb4e4bdd33d2f5820f7f9b395513968132969 Mon Sep 17 00:00:00 2001
From: TaprootFreak <142087526+TaprootFreak@users.noreply.github.com>
Date: Sat, 6 Sep 2025 14:38:15 +0200
Subject: [PATCH 1/3] feat: add message signing for Rootstock network
- Add Sign Message button in Developer Options (only visible on Rootstock)
- Create SignMessage screen with EVM-compatible message signing
- Reuse existing signPersonalMessage from BackgroundExecutor
- Include signature display and copy-to-clipboard functionality
---
mobile/app/Settings.tsx | 11 ++
mobile/app/SignMessage.tsx | 268 +++++++++++++++++++++++++++++++++++++
2 files changed, 279 insertions(+)
create mode 100644 mobile/app/SignMessage.tsx
diff --git a/mobile/app/Settings.tsx b/mobile/app/Settings.tsx
index e5f15c18..e37469fb 100644
--- a/mobile/app/Settings.tsx
+++ b/mobile/app/Settings.tsx
@@ -20,6 +20,7 @@ import { SETTINGS_CONFIG } from '@shared/hooks/SettingsContext';
import { useSettings } from '@shared/hooks/useSettings';
import { capitalizeFirstLetter } from '@shared/modules/string-utils';
import { STORAGE_KEY_BTC_XPUB, STORAGE_KEY_MNEMONIC } from '@shared/types/IStorage';
+import { NETWORK_ROOTSTOCK } from '@shared/types/networks';
import { BackgroundExecutor } from '@/src/modules/background-executor';
const gitCommitHash = require('../git_commit_hash.json');
@@ -231,6 +232,16 @@ export default function SettingsScreen() {
>
ScanQr
+
+ {network === NETWORK_ROOTSTOCK && (
+ router.push('/SignMessage')}
+ testID="SignMessageButton"
+ >
+ Sign Message
+
+ )}
diff --git a/mobile/app/SignMessage.tsx b/mobile/app/SignMessage.tsx
new file mode 100644
index 00000000..4e9b608d
--- /dev/null
+++ b/mobile/app/SignMessage.tsx
@@ -0,0 +1,268 @@
+import { Ionicons } from '@expo/vector-icons';
+import { Stack, useRouter } from 'expo-router';
+import React, { useContext, useState } from 'react';
+import { View, ScrollView, StyleSheet, TextInput, TouchableOpacity, Alert, Text, Clipboard } from 'react-native';
+
+import GradientScreen from '@/components/GradientScreen';
+import ScreenHeader from '@/components/navigation/ScreenHeader';
+import { ThemedText } from '@/components/ThemedText';
+import { AskPasswordContext } from '@/src/hooks/AskPasswordContext';
+import { BackgroundExecutor } from '@/src/modules/background-executor';
+import { AccountNumberContext } from '@shared/hooks/AccountNumberContext';
+import { NetworkContext } from '@shared/hooks/NetworkContext';
+import { NETWORK_ROOTSTOCK } from '@shared/types/networks';
+
+const SignMessage = () => {
+ const router = useRouter();
+ const { network } = useContext(NetworkContext);
+ const { accountNumber } = useContext(AccountNumberContext);
+ const { askPassword } = useContext(AskPasswordContext);
+
+ const [message, setMessage] = useState('');
+ const [signature, setSignature] = useState('');
+ const [isLoading, setIsLoading] = useState(false);
+
+ // This screen should only be accessible from Rootstock network
+ // But we'll handle it gracefully if accessed from elsewhere
+ const isRootstock = network === NETWORK_ROOTSTOCK;
+
+ const handleSign = async () => {
+ if (!message.trim()) {
+ Alert.alert('Error', 'Please enter a message to sign');
+ return;
+ }
+
+ if (!isRootstock) {
+ Alert.alert('Error', 'Message signing is only available on Rootstock network');
+ return;
+ }
+
+ setIsLoading(true);
+ try {
+ const password = await askPassword();
+
+ // Use EVM signing for Rootstock (it's an EVM-compatible chain)
+ const result = await BackgroundExecutor.signPersonalMessage(
+ message,
+ accountNumber,
+ password
+ );
+
+ if (result.success) {
+ setSignature(result.bytes);
+ Alert.alert('Success', 'Message signed successfully!');
+ } else {
+ throw new Error(result.message || 'Failed to sign message');
+ }
+ } catch (error: any) {
+ Alert.alert('Error', error.message || 'Failed to sign message');
+ } finally {
+ setIsLoading(false);
+ }
+ };
+
+ const copyToClipboard = () => {
+ if (signature) {
+ Clipboard.setString(signature);
+ Alert.alert('Copied', 'Signature copied to clipboard');
+ }
+ };
+
+ return (
+
+
+
+
+
+
+ {isRootstock ? (
+ <>
+
+ Sign a message with your Rootstock private key. This creates a cryptographic proof
+ that you control this wallet address on the Rootstock network.
+
+
+
+ Message to Sign
+
+
+
+
+
+
+ {isLoading ? 'Signing...' : 'Sign Message'}
+
+
+
+ {signature ? (
+
+ Rootstock Signature:
+
+
+ {signature}
+
+
+
+
+ Copy Signature
+
+
+ ) : null}
+
+
+
+
+ This signature proves you control the private key for account #{accountNumber} on the Rootstock network without revealing the key itself.
+
+
+ >
+ ) : (
+
+
+
+ Message signing is only available when using the Rootstock network.
+
+
+ Please switch to Rootstock from the home screen to use this feature.
+
+
+ )}
+
+
+
+ );
+};
+
+const styles = StyleSheet.create({
+ scrollContent: {
+ flexGrow: 1,
+ paddingHorizontal: 16,
+ },
+ contentContainer: {
+ flex: 1,
+ paddingVertical: 20,
+ },
+ description: {
+ color: 'rgba(255, 255, 255, 0.8)',
+ marginBottom: 30,
+ lineHeight: 20,
+ },
+ inputSection: {
+ marginBottom: 20,
+ },
+ inputLabel: {
+ color: 'rgba(255, 255, 255, 0.9)',
+ marginBottom: 10,
+ fontWeight: '500',
+ },
+ messageInput: {
+ backgroundColor: 'rgba(255, 255, 255, 0.1)',
+ borderWidth: 1,
+ borderColor: 'rgba(255, 255, 255, 0.2)',
+ borderRadius: 12,
+ padding: 15,
+ color: 'rgba(255, 255, 255, 0.9)',
+ minHeight: 120,
+ fontSize: 14,
+ },
+ signButton: {
+ backgroundColor: '#000000',
+ borderRadius: 12,
+ paddingVertical: 15,
+ flexDirection: 'row',
+ alignItems: 'center',
+ justifyContent: 'center',
+ gap: 8,
+ marginBottom: 30,
+ },
+ buttonDisabled: {
+ opacity: 0.6,
+ },
+ signButtonText: {
+ color: 'rgba(255, 255, 255, 0.9)',
+ fontWeight: '600',
+ },
+ resultSection: {
+ backgroundColor: 'rgba(0, 255, 0, 0.05)',
+ borderWidth: 1,
+ borderColor: 'rgba(0, 255, 0, 0.2)',
+ borderRadius: 12,
+ padding: 15,
+ marginBottom: 30,
+ },
+ resultLabel: {
+ color: 'rgba(255, 255, 255, 0.7)',
+ marginBottom: 10,
+ fontSize: 12,
+ },
+ signatureContainer: {
+ backgroundColor: 'rgba(0, 0, 0, 0.3)',
+ borderRadius: 8,
+ padding: 10,
+ marginBottom: 10,
+ },
+ signatureText: {
+ color: 'rgba(0, 255, 0, 0.8)',
+ fontFamily: 'monospace',
+ fontSize: 12,
+ },
+ copyButton: {
+ flexDirection: 'row',
+ alignItems: 'center',
+ justifyContent: 'center',
+ gap: 6,
+ paddingVertical: 8,
+ backgroundColor: 'rgba(255, 255, 255, 0.1)',
+ borderRadius: 8,
+ },
+ copyButtonText: {
+ color: 'rgba(255, 255, 255, 0.8)',
+ fontSize: 14,
+ },
+ infoSection: {
+ flexDirection: 'row',
+ gap: 10,
+ backgroundColor: 'rgba(255, 255, 255, 0.05)',
+ padding: 15,
+ borderRadius: 12,
+ },
+ infoText: {
+ flex: 1,
+ color: 'rgba(255, 255, 255, 0.6)',
+ fontSize: 12,
+ lineHeight: 18,
+ },
+ unsupportedSection: {
+ flex: 1,
+ justifyContent: 'center',
+ alignItems: 'center',
+ paddingVertical: 60,
+ },
+ unsupportedText: {
+ color: 'rgba(255, 255, 255, 0.6)',
+ fontSize: 16,
+ textAlign: 'center',
+ marginTop: 20,
+ marginBottom: 10,
+ },
+ unsupportedSubtext: {
+ color: 'rgba(255, 255, 255, 0.4)',
+ fontSize: 14,
+ textAlign: 'center',
+ paddingHorizontal: 20,
+ },
+});
+
+export default SignMessage;
\ No newline at end of file
From a5b61c6c6730969e7f026cdbb14b3eb1da8533c4 Mon Sep 17 00:00:00 2001
From: TaprootFreak <142087526+TaprootFreak@users.noreply.github.com>
Date: Mon, 15 Sep 2025 20:51:09 +0200
Subject: [PATCH 2/3] Enable message signing for all EVM-compatible networks
(#7)
* Enable message signing for all EVM-compatible networks
- Extend SignMessage module to support all EVM chains (Rootstock, Botanix, etc.)
- Replace Rootstock-specific checks with generic EVM network detection
- Update UI text to reflect multi-network support
* Show Sign Message button for all EVM networks in Settings
* Add signing address display to SignMessage screen
- Display the EVM address that will be used for signing
- Add copy functionality for the address
- Show address field above the message input
- Works for all EVM-compatible networks
* Update comment to reflect multi-chain EVM support
---
mobile/app/Settings.tsx | 4 +-
mobile/app/SignMessage.tsx | 84 +++++++++++++++++++++++++++++++-------
2 files changed, 71 insertions(+), 17 deletions(-)
diff --git a/mobile/app/Settings.tsx b/mobile/app/Settings.tsx
index e37469fb..dbc6995b 100644
--- a/mobile/app/Settings.tsx
+++ b/mobile/app/Settings.tsx
@@ -20,7 +20,7 @@ import { SETTINGS_CONFIG } from '@shared/hooks/SettingsContext';
import { useSettings } from '@shared/hooks/useSettings';
import { capitalizeFirstLetter } from '@shared/modules/string-utils';
import { STORAGE_KEY_BTC_XPUB, STORAGE_KEY_MNEMONIC } from '@shared/types/IStorage';
-import { NETWORK_ROOTSTOCK } from '@shared/types/networks';
+import { getIsEVM } from '@shared/models/network-getters';
import { BackgroundExecutor } from '@/src/modules/background-executor';
const gitCommitHash = require('../git_commit_hash.json');
@@ -233,7 +233,7 @@ export default function SettingsScreen() {
ScanQr
- {network === NETWORK_ROOTSTOCK && (
+ {network && getIsEVM(network) && (
router.push('/SignMessage')}
diff --git a/mobile/app/SignMessage.tsx b/mobile/app/SignMessage.tsx
index 4e9b608d..3b8f4485 100644
--- a/mobile/app/SignMessage.tsx
+++ b/mobile/app/SignMessage.tsx
@@ -10,21 +10,37 @@ import { AskPasswordContext } from '@/src/hooks/AskPasswordContext';
import { BackgroundExecutor } from '@/src/modules/background-executor';
import { AccountNumberContext } from '@shared/hooks/AccountNumberContext';
import { NetworkContext } from '@shared/hooks/NetworkContext';
-import { NETWORK_ROOTSTOCK } from '@shared/types/networks';
+import { getIsEVM } from '@shared/models/network-getters';
const SignMessage = () => {
const router = useRouter();
const { network } = useContext(NetworkContext);
const { accountNumber } = useContext(AccountNumberContext);
const { askPassword } = useContext(AskPasswordContext);
-
+
const [message, setMessage] = useState('');
const [signature, setSignature] = useState('');
const [isLoading, setIsLoading] = useState(false);
+ const [address, setAddress] = useState('');
+
+ // This screen is available for all EVM-compatible networks
+ // But we'll handle it gracefully if accessed from non-EVM networks
+ const isEVMNetwork = network ? getIsEVM(network) : false;
- // This screen should only be accessible from Rootstock network
- // But we'll handle it gracefully if accessed from elsewhere
- const isRootstock = network === NETWORK_ROOTSTOCK;
+ // Fetch the EVM address when component mounts or network/account changes
+ React.useEffect(() => {
+ const fetchAddress = async () => {
+ if (isEVMNetwork && network) {
+ try {
+ const evmAddress = await BackgroundExecutor.getAddress(network, accountNumber);
+ setAddress(evmAddress);
+ } catch (error) {
+ console.error('Failed to fetch EVM address:', error);
+ }
+ }
+ };
+ fetchAddress();
+ }, [network, accountNumber, isEVMNetwork]);
const handleSign = async () => {
if (!message.trim()) {
@@ -32,8 +48,8 @@ const SignMessage = () => {
return;
}
- if (!isRootstock) {
- Alert.alert('Error', 'Message signing is only available on Rootstock network');
+ if (!isEVMNetwork) {
+ Alert.alert('Error', 'Message signing is only available on EVM-compatible networks');
return;
}
@@ -41,7 +57,7 @@ const SignMessage = () => {
try {
const password = await askPassword();
- // Use EVM signing for Rootstock (it's an EVM-compatible chain)
+ // Use EVM signing for all EVM-compatible chains
const result = await BackgroundExecutor.signPersonalMessage(
message,
accountNumber,
@@ -75,13 +91,31 @@ const SignMessage = () => {
- {isRootstock ? (
+ {isEVMNetwork ? (
<>
- Sign a message with your Rootstock private key. This creates a cryptographic proof
- that you control this wallet address on the Rootstock network.
+ Sign a message with your EVM private key. This creates a cryptographic proof
+ that you control this wallet address on the {network} network.
+
+ Signing Address
+
+ {address || 'Loading...'}
+ {address && (
+ {
+ Clipboard.setString(address);
+ Alert.alert('Copied', 'Address copied to clipboard');
+ }}
+ style={styles.copyIcon}
+ >
+
+
+ )}
+
+
+
Message to Sign
{
{signature ? (
- Rootstock Signature:
+ EVM Signature:
{signature}
@@ -124,7 +158,7 @@ const SignMessage = () => {
- This signature proves you control the private key for account #{accountNumber} on the Rootstock network without revealing the key itself.
+ This signature proves you control the private key for account #{accountNumber} on the {network} network without revealing the key itself.
>
@@ -132,10 +166,10 @@ const SignMessage = () => {
- Message signing is only available when using the Rootstock network.
+ Message signing is only available on EVM-compatible networks.
- Please switch to Rootstock from the home screen to use this feature.
+ Please switch to an EVM-compatible network (Rootstock, Botanix, etc.) from the home screen to use this feature.
)}
@@ -167,6 +201,26 @@ const styles = StyleSheet.create({
marginBottom: 10,
fontWeight: '500',
},
+ addressContainer: {
+ backgroundColor: 'rgba(255, 255, 255, 0.1)',
+ borderWidth: 1,
+ borderColor: 'rgba(255, 255, 255, 0.2)',
+ borderRadius: 12,
+ padding: 15,
+ flexDirection: 'row',
+ alignItems: 'center',
+ justifyContent: 'space-between',
+ },
+ addressText: {
+ color: 'rgba(255, 255, 255, 0.7)',
+ fontSize: 13,
+ fontFamily: 'monospace',
+ flex: 1,
+ },
+ copyIcon: {
+ marginLeft: 10,
+ padding: 4,
+ },
messageInput: {
backgroundColor: 'rgba(255, 255, 255, 0.1)',
borderWidth: 1,
From 3fe5934385d8d73a1662de778a2993b979a0d362 Mon Sep 17 00:00:00 2001
From: TaprootFreak <142087526+TaprootFreak@users.noreply.github.com>
Date: Mon, 15 Sep 2025 22:48:18 +0200
Subject: [PATCH 3/3] Add Spark network message signing support (#8)
* Add Spark network message signing support
- Implement signMessageWithIdentityKey in SparkWallet class
- Add signSparkMessage to BackgroundExecutor
- Update SignMessage UI to support both EVM and Spark networks
- Skip password prompt for Spark (uses unencrypted submnemonics)
- Add validation method for signature verification
- Update Settings to show Sign Message button for Spark network
* Revert .gitignore changes
---
mobile/app/Settings.tsx | 3 +-
mobile/app/SignMessage.tsx | 62 ++++++++++++++---------
mobile/src/modules/background-executor.ts | 38 ++++++++++++++
shared/class/wallets/spark-wallet.ts | 45 ++++++++++++++++
4 files changed, 122 insertions(+), 26 deletions(-)
diff --git a/mobile/app/Settings.tsx b/mobile/app/Settings.tsx
index dbc6995b..88968bdf 100644
--- a/mobile/app/Settings.tsx
+++ b/mobile/app/Settings.tsx
@@ -21,6 +21,7 @@ import { useSettings } from '@shared/hooks/useSettings';
import { capitalizeFirstLetter } from '@shared/modules/string-utils';
import { STORAGE_KEY_BTC_XPUB, STORAGE_KEY_MNEMONIC } from '@shared/types/IStorage';
import { getIsEVM } from '@shared/models/network-getters';
+import { NETWORK_SPARK } from '@shared/types/networks';
import { BackgroundExecutor } from '@/src/modules/background-executor';
const gitCommitHash = require('../git_commit_hash.json');
@@ -233,7 +234,7 @@ export default function SettingsScreen() {
ScanQr
- {network && getIsEVM(network) && (
+ {network && (getIsEVM(network) || network === NETWORK_SPARK) && (
router.push('/SignMessage')}
diff --git a/mobile/app/SignMessage.tsx b/mobile/app/SignMessage.tsx
index 3b8f4485..1870f56d 100644
--- a/mobile/app/SignMessage.tsx
+++ b/mobile/app/SignMessage.tsx
@@ -11,6 +11,7 @@ import { BackgroundExecutor } from '@/src/modules/background-executor';
import { AccountNumberContext } from '@shared/hooks/AccountNumberContext';
import { NetworkContext } from '@shared/hooks/NetworkContext';
import { getIsEVM } from '@shared/models/network-getters';
+import { NETWORK_SPARK } from '@shared/types/networks';
const SignMessage = () => {
const router = useRouter();
@@ -23,24 +24,26 @@ const SignMessage = () => {
const [isLoading, setIsLoading] = useState(false);
const [address, setAddress] = useState('');
- // This screen is available for all EVM-compatible networks
- // But we'll handle it gracefully if accessed from non-EVM networks
+ // This screen is available for all EVM-compatible networks and Spark
+ // But we'll handle it gracefully if accessed from unsupported networks
const isEVMNetwork = network ? getIsEVM(network) : false;
+ const isSparkNetwork = network === NETWORK_SPARK;
+ const isSupported = isEVMNetwork || isSparkNetwork;
- // Fetch the EVM address when component mounts or network/account changes
+ // Fetch the address when component mounts or network/account changes
React.useEffect(() => {
const fetchAddress = async () => {
- if (isEVMNetwork && network) {
+ if (isSupported && network) {
try {
- const evmAddress = await BackgroundExecutor.getAddress(network, accountNumber);
- setAddress(evmAddress);
+ const walletAddress = await BackgroundExecutor.getAddress(network, accountNumber);
+ setAddress(walletAddress);
} catch (error) {
- console.error('Failed to fetch EVM address:', error);
+ console.error('Failed to fetch address:', error);
}
}
};
fetchAddress();
- }, [network, accountNumber, isEVMNetwork]);
+ }, [network, accountNumber, isSupported]);
const handleSign = async () => {
if (!message.trim()) {
@@ -48,24 +51,33 @@ const SignMessage = () => {
return;
}
- if (!isEVMNetwork) {
- Alert.alert('Error', 'Message signing is only available on EVM-compatible networks');
+ if (!isSupported) {
+ Alert.alert('Error', 'Message signing is only available on EVM-compatible networks and Spark');
return;
}
setIsLoading(true);
try {
- const password = await askPassword();
-
- // Use EVM signing for all EVM-compatible chains
- const result = await BackgroundExecutor.signPersonalMessage(
- message,
- accountNumber,
- password
- );
+ let result;
+ if (isSparkNetwork) {
+ // Spark doesn't need password as it uses unencrypted submnemonics
+ result = await BackgroundExecutor.signSparkMessage(
+ message,
+ accountNumber,
+ null // No password needed for Spark
+ );
+ } else {
+ // EVM chains need password to decrypt the mnemonic
+ const password = await askPassword();
+ result = await BackgroundExecutor.signPersonalMessage(
+ message,
+ accountNumber,
+ password
+ );
+ }
- if (result.success) {
- setSignature(result.bytes);
+ if (result?.success) {
+ setSignature(result.bytes || result.signature);
Alert.alert('Success', 'Message signed successfully!');
} else {
throw new Error(result.message || 'Failed to sign message');
@@ -91,10 +103,10 @@ const SignMessage = () => {
- {isEVMNetwork ? (
+ {isSupported ? (
<>
- Sign a message with your EVM private key. This creates a cryptographic proof
+ Sign a message with your {isSparkNetwork ? 'Spark identity' : 'EVM private'} key. This creates a cryptographic proof
that you control this wallet address on the {network} network.
@@ -142,7 +154,7 @@ const SignMessage = () => {
{signature ? (
- EVM Signature:
+ {isSparkNetwork ? 'Spark' : 'EVM'} Signature:
{signature}
@@ -166,10 +178,10 @@ const SignMessage = () => {
- Message signing is only available on EVM-compatible networks.
+ Message signing is only available on EVM-compatible networks and Spark.
- Please switch to an EVM-compatible network (Rootstock, Botanix, etc.) from the home screen to use this feature.
+ Please switch to a supported network (Rootstock, Botanix, Spark, etc.) from the home screen to use this feature.
)}
diff --git a/mobile/src/modules/background-executor.ts b/mobile/src/modules/background-executor.ts
index 437ed0bc..11a1dd9d 100644
--- a/mobile/src/modules/background-executor.ts
+++ b/mobile/src/modules/background-executor.ts
@@ -230,6 +230,44 @@ export const BackgroundExecutor: IBackgroundCaller = {
}
},
+ async signSparkMessage(message, accountNumber, password) {
+ try {
+ // Get the submnemonic for the account
+ const submnemonic = await SecureStorage.getItem(STORAGE_KEY_SUB_MNEMONIC + accountNumber);
+
+ if (!submnemonic) {
+ return {
+ success: false,
+ signature: '',
+ message: 'No submnemonic found for this account. Please reinstall the app.',
+ };
+ }
+
+ // Get or initialize the Spark wallet
+ const wallet = await BackgroundExecutor.lazyInitWallet(NETWORK_SPARK, accountNumber);
+
+ if (!(wallet instanceof SparkWallet)) {
+ return {
+ success: false,
+ signature: '',
+ message: 'Failed to initialize Spark wallet',
+ };
+ }
+
+ // Sign the message with the identity key
+ const signature = await wallet.signMessageWithIdentityKey(message);
+
+ return { success: true, signature };
+ } catch (error: any) {
+ console.error('Error signing Spark message:', error);
+ return {
+ success: false,
+ signature: '',
+ message: error.message || 'Failed to sign message with Spark wallet',
+ };
+ }
+ },
+
async openPopup(...params: OpenPopupRequest) {
const bridge = BrowserBridge.getInstance();
if (bridge) {
diff --git a/shared/class/wallets/spark-wallet.ts b/shared/class/wallets/spark-wallet.ts
index 91fea942..511f46d0 100644
--- a/shared/class/wallets/spark-wallet.ts
+++ b/shared/class/wallets/spark-wallet.ts
@@ -186,4 +186,49 @@ export class SparkWallet extends ArkWallet implements InterfaceLightningWallet {
return await this._sdkWallet.transferTokens({ receiverSparkAddress, tokenAmount, tokenIdentifier: tokenIdentifier as Bech32mTokenIdentifier });
}
+
+ /**
+ * Sign a message with the wallet's identity key
+ * @param message - The message to sign (string or Uint8Array)
+ * @param compact - Whether to use compact signature format (default: true)
+ * @returns The signature as a hex string
+ */
+ async signMessageWithIdentityKey(message: string | Uint8Array, compact: boolean = true): Promise {
+ if (!this._sdkWallet) throw new Error('Spark wallet not initialized');
+
+ // Convert string to Uint8Array if needed
+ const messageBytes = typeof message === 'string'
+ ? new TextEncoder().encode(message)
+ : message;
+
+ // Sign the message using the SDK's signMessageWithIdentityKey method
+ const signature = await this._sdkWallet.signMessageWithIdentityKey(messageBytes, compact);
+
+ // Convert signature to hex string if it's a Uint8Array
+ if (signature instanceof Uint8Array) {
+ return Buffer.from(signature).toString('hex');
+ }
+
+ return signature;
+ }
+
+ /**
+ * Validate a message signature against the identity key
+ * @param message - The original message
+ * @param signature - The signature to validate
+ * @returns True if the signature is valid
+ */
+ async validateMessageWithIdentityKey(message: string | Uint8Array, signature: string | Uint8Array): Promise {
+ if (!this._sdkWallet) throw new Error('Spark wallet not initialized');
+
+ const messageBytes = typeof message === 'string'
+ ? new TextEncoder().encode(message)
+ : message;
+
+ const signatureBytes = typeof signature === 'string'
+ ? Buffer.from(signature, 'hex')
+ : signature;
+
+ return await this._sdkWallet.validateMessageWithIdentityKey(messageBytes, signatureBytes);
+ }
}