chore: clean up services

This commit is contained in:
Danish Arora 2025-08-28 18:44:35 +05:30
parent 62d6800f0b
commit 9b6a1c48d7
No known key found for this signature in database
GPG Key ID: 1C6EF37CDAE1426E
12 changed files with 279 additions and 297 deletions

View File

@ -2,7 +2,7 @@ import React from 'react';
import { Button } from './button';
import { useAuth } from '@/contexts/useAuth';
import { CheckCircle, AlertCircle, Trash2 } from 'lucide-react';
import { DelegationDuration } from '@/lib/identity/signatures/key-delegation';
import { DelegationDuration } from '@/lib/identity/services/CryptoService';
interface DelegationStepProps {
onComplete: () => void;

View File

@ -1,10 +1,9 @@
import React, { createContext, useContext, useState, useEffect, useRef } from 'react';
import { useToast } from '@/components/ui/use-toast';
import { User } from '@/types';
import { AuthService, AuthResult } from '@/lib/identity/services/AuthService';
import { OpchanMessage } from '@/types';
import { User, OpchanMessage, EVerificationStatus } from '@/types/forum';
import { AuthService, CryptoService, MessageService, DelegationDuration } from '@/lib/identity/services';
import { AuthResult } from '@/lib/identity/services/AuthService';
import { useAppKitAccount, useDisconnect, modal } from '@reown/appkit/react';
import { DelegationDuration } from '@/lib/identity/signatures/key-delegation';
export type VerificationStatus = 'unverified' | 'verified-none' | 'verified-basic' | 'verified-owner' | 'verifying';
@ -47,8 +46,10 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
const activeAccount = isBitcoinConnected ? bitcoinAccount : ethereumAccount;
const address = activeAccount.address;
// Create ref for AuthService so it persists between renders
const authServiceRef = useRef(new AuthService());
// Create service instances that persist between renders
const cryptoServiceRef = useRef(new CryptoService());
const authServiceRef = useRef(new AuthService(cryptoServiceRef.current));
const messageServiceRef = useRef(new MessageService(authServiceRef.current, cryptoServiceRef.current));
// Set AppKit instance and accounts in AuthService
useEffect(() => {
@ -76,7 +77,7 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
const newUser: User = {
address,
walletType: isBitcoinConnected ? 'bitcoin' : 'ethereum',
verificationStatus: 'verified-basic', // Connected wallets get basic verification by default
verificationStatus: EVerificationStatus.VERIFIED_BASIC, // Connected wallets get basic verification by default
lastChecked: Date.now(),
};
@ -88,7 +89,7 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
...newUser,
ensOwnership: true,
ensName: walletInfo.ensName,
verificationStatus: 'verified-owner' as const,
verificationStatus: EVerificationStatus.VERIFIED_OWNER,
};
setCurrentUser(updatedUser);
setVerificationStatus('verified-owner');
@ -296,15 +297,15 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
};
const isDelegationValid = (): boolean => {
return authServiceRef.current.isDelegationValid();
return cryptoServiceRef.current.isDelegationValid();
};
const delegationTimeRemaining = (): number => {
return authServiceRef.current.getDelegationTimeRemaining();
return cryptoServiceRef.current.getDelegationTimeRemaining();
};
const clearDelegation = (): void => {
authServiceRef.current.clearDelegation();
cryptoServiceRef.current.clearDelegation();
// Update the current user to remove delegation info
if (currentUser) {
@ -329,10 +330,10 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
const messageSigning = {
signMessage: async (message: OpchanMessage): Promise<OpchanMessage | null> => {
return authServiceRef.current.signMessage(message);
return cryptoServiceRef.current.signMessage(message);
},
verifyMessage: (message: OpchanMessage): boolean => {
return authServiceRef.current.verifyMessage(message);
return cryptoServiceRef.current.verifyMessage(message);
}
};

View File

@ -1,5 +1,5 @@
import { RelevanceCalculator } from '../relevance';
import { Post, Comment, User, UserVerificationStatus } from '@/types/forum';
import { Post, Comment, User, UserVerificationStatus, EVerificationStatus } from '@/types/forum';
import { VoteMessage, MessageType } from '@/lib/waku/types';
import { expect, describe, beforeEach, it } from 'vitest';
@ -58,7 +58,7 @@ describe('RelevanceCalculator', () => {
const verifiedUser: User = {
address: 'user1',
walletType: 'ethereum',
verificationStatus: 'verified-owner',
verificationStatus: EVerificationStatus.VERIFIED_OWNER,
ensOwnership: true,
ensName: 'test.eth',
lastChecked: Date.now()
@ -72,7 +72,7 @@ describe('RelevanceCalculator', () => {
const verifiedUser: User = {
address: 'user3',
walletType: 'bitcoin',
verificationStatus: 'verified-owner',
verificationStatus: EVerificationStatus.VERIFIED_OWNER,
ordinalOwnership: true,
lastChecked: Date.now()
};
@ -85,7 +85,7 @@ describe('RelevanceCalculator', () => {
const unverifiedUser: User = {
address: 'user2',
walletType: 'ethereum',
verificationStatus: 'unverified',
verificationStatus: EVerificationStatus.UNVERIFIED,
ensOwnership: false,
lastChecked: Date.now()
};
@ -184,7 +184,7 @@ describe('RelevanceCalculator', () => {
{
address: 'user1',
walletType: 'ethereum',
verificationStatus: 'verified-owner',
verificationStatus: EVerificationStatus.VERIFIED_OWNER,
ensOwnership: true,
ensName: 'test.eth',
lastChecked: Date.now()
@ -192,7 +192,7 @@ describe('RelevanceCalculator', () => {
{
address: 'user2',
walletType: 'bitcoin',
verificationStatus: 'unverified',
verificationStatus: EVerificationStatus.UNVERIFIED,
ordinalOwnership: false,
lastChecked: Date.now()
}

View File

@ -9,8 +9,7 @@ import {
} from '@/lib/waku/types';
import { Cell, Comment, Post, User } from '@/types/forum';
import { transformCell, transformComment, transformPost } from './transformers';
import { MessageService } from '@/lib/identity/services/MessageService';
import { AuthService } from '@/lib/identity/services/AuthService';
import { MessageService, AuthService, CryptoService } from '@/lib/identity/services';
type ToastFunction = (props: {
title: string;
@ -70,8 +69,9 @@ export const createPost = async (
author: currentUser.address,
};
const messageService = new MessageService(authService!);
const result = await messageService.signAndSendMessage(postMessage);
const cryptoService = new CryptoService();
const messageService = new MessageService(authService!, cryptoService);
const result = await messageService.sendMessage(postMessage);
if (!result.success) {
toast({
title: 'Post Failed',
@ -141,8 +141,9 @@ export const createComment = async (
author: currentUser.address,
};
const messageService = new MessageService(authService!);
const result = await messageService.signAndSendMessage(commentMessage);
const cryptoService = new CryptoService();
const messageService = new MessageService(authService!, cryptoService);
const result = await messageService.sendMessage(commentMessage);
if (!result.success) {
toast({
title: 'Comment Failed',
@ -199,8 +200,9 @@ export const createCell = async (
author: currentUser.address,
};
const messageService = new MessageService(authService!);
const result = await messageService.signAndSendMessage(cellMessage);
const cryptoService = new CryptoService();
const messageService = new MessageService(authService!, cryptoService);
const result = await messageService.sendMessage(cellMessage);
if (!result.success) {
toast({
title: 'Cell Failed',
@ -275,8 +277,9 @@ export const vote = async (
author: currentUser.address,
};
const messageService = new MessageService(authService!);
const result = await messageService.signAndSendMessage(voteMessage);
const cryptoService = new CryptoService();
const messageService = new MessageService(authService!, cryptoService);
const result = await messageService.sendMessage(voteMessage);
if (!result.success) {
toast({
title: 'Vote Failed',
@ -340,8 +343,9 @@ export const moderatePost = async (
timestamp: Date.now(),
author: currentUser.address,
};
const messageService = new MessageService(authService!);
const result = await messageService.signAndSendMessage(modMsg);
const cryptoService = new CryptoService();
const messageService = new MessageService(authService!, cryptoService);
const result = await messageService.sendMessage(modMsg);
if (!result.success) {
toast({ title: 'Moderation Failed', description: result.error || 'Failed to moderate post. Please try again.', variant: 'destructive' });
return false;
@ -389,8 +393,9 @@ export const moderateComment = async (
timestamp: Date.now(),
author: currentUser.address,
};
const messageService = new MessageService(authService!);
const result = await messageService.signAndSendMessage(modMsg);
const cryptoService = new CryptoService();
const messageService = new MessageService(authService!, cryptoService);
const result = await messageService.sendMessage(modMsg);
if (!result.success) {
toast({ title: 'Moderation Failed', description: result.error || 'Failed to moderate comment. Please try again.', variant: 'destructive' });
return false;
@ -437,8 +442,9 @@ export const moderateUser = async (
signature: '',
browserPubKey: currentUser.browserPubKey,
};
const messageService = new MessageService(authService!);
const result = await messageService.signAndSendMessage(modMsg);
const cryptoService = new CryptoService();
const messageService = new MessageService(authService!, cryptoService);
const result = await messageService.sendMessage(modMsg);
if (!result.success) {
toast({ title: 'Moderation Failed', description: result.error || 'Failed to moderate user. Please try again.', variant: 'destructive' });
return false;

View File

@ -1,11 +1,9 @@
import { User } from '@/types';
import { WalletService } from '../wallets/index';
import { UseAppKitAccountReturn } from '@reown/appkit/react';
import { AppKit } from '@reown/appkit';
import { OrdinalAPI } from '../ordinal';
import { MessageSigning } from '../signatures/message-signing';
import { KeyDelegation, DelegationDuration } from '../signatures/key-delegation';
import { OpchanMessage } from '@/types';
import { CryptoService, DelegationDuration } from './CryptoService';
import { EVerificationStatus, User } from '@/types/forum';
export interface AuthResult {
success: boolean;
@ -13,17 +11,37 @@ export interface AuthResult {
error?: string;
}
export class AuthService {
export interface AuthServiceInterface {
// Wallet operations
setAccounts(bitcoinAccount: UseAppKitAccountReturn, ethereumAccount: UseAppKitAccountReturn): void;
setAppKit(appKit: AppKit): void;
connectWallet(): Promise<AuthResult>;
disconnectWallet(): Promise<void>;
// Verification
verifyOwnership(user: User): Promise<AuthResult>;
// Delegation setup
delegateKey(user: User, duration?: DelegationDuration): Promise<AuthResult>;
// User persistence
loadStoredUser(): User | null;
saveUser(user: User): void;
clearStoredUser(): void;
// Wallet info
getWalletInfo(): Promise<any>;
}
export class AuthService implements AuthServiceInterface {
private walletService: WalletService;
private ordinalApi: OrdinalAPI;
private messageSigning: MessageSigning;
private keyDelegation: KeyDelegation;
private cryptoService: CryptoService;
constructor() {
constructor(cryptoService: CryptoService) {
this.walletService = new WalletService();
this.ordinalApi = new OrdinalAPI();
this.keyDelegation = new KeyDelegation();
this.messageSigning = new MessageSigning(this.keyDelegation);
this.cryptoService = cryptoService;
}
/**
@ -98,7 +116,7 @@ export class AuthService {
const user: User = {
address: address,
walletType: walletType,
verificationStatus: 'unverified',
verificationStatus: EVerificationStatus.UNVERIFIED,
lastChecked: Date.now(),
};
@ -132,7 +150,7 @@ export class AuthService {
*/
async disconnectWallet(): Promise<void> {
// Clear any existing delegations when disconnecting
this.keyDelegation.clearDelegation();
this.cryptoService.clearDelegation();
this.walletService.clearDelegation('bitcoin');
this.walletService.clearDelegation('ethereum');
@ -140,13 +158,6 @@ export class AuthService {
this.clearStoredUser();
}
/**
* Clear delegation for current wallet
*/
clearDelegation(): void {
this.keyDelegation.clearDelegation();
}
/**
* Verify ordinal ownership for Bitcoin users or ENS ownership for Ethereum users
*/
@ -182,7 +193,7 @@ export class AuthService {
const updatedUser = {
...user,
ordinalOwnership: hasOperators,
verificationStatus: hasOperators ? 'verified-owner' : 'verified-basic',
verificationStatus: hasOperators ? EVerificationStatus.VERIFIED_OWNER : EVerificationStatus.VERIFIED_BASIC,
lastChecked: Date.now(),
};
@ -207,7 +218,7 @@ export class AuthService {
...user,
ensOwnership: hasENS,
ensName: ensName,
verificationStatus: hasENS ? 'verified-owner' : 'verified-basic',
verificationStatus: hasENS ? EVerificationStatus.VERIFIED_OWNER : EVerificationStatus.VERIFIED_BASIC,
lastChecked: Date.now(),
};
@ -223,7 +234,7 @@ export class AuthService {
...user,
ensOwnership: false,
ensName: undefined,
verificationStatus: 'verified-basic',
verificationStatus: EVerificationStatus.VERIFIED_BASIC,
lastChecked: Date.now(),
};
@ -262,7 +273,7 @@ export class AuthService {
const delegationStatus = this.walletService.getDelegationStatus(walletType);
// Get the actual browser public key from the delegation
const browserPublicKey = this.keyDelegation.getBrowserPublicKey();
const browserPublicKey = this.cryptoService.getBrowserPublicKey();
const updatedUser = {
...user,
@ -283,44 +294,6 @@ export class AuthService {
}
}
/**
* Sign a message using delegated key
*/
async signMessage(message: OpchanMessage): Promise<OpchanMessage | null> {
return this.messageSigning.signMessage(message);
}
/**
* Verify a message signature
*/
verifyMessage(message: OpchanMessage): boolean {
return this.messageSigning.verifyMessage(message);
}
/**
* Check if delegation is valid
*/
isDelegationValid(): boolean {
// Only check the currently connected wallet type
const activeWalletType = this.getActiveWalletType();
if (!activeWalletType) return false;
const status = this.walletService.getDelegationStatus(activeWalletType);
return status.isValid;
}
/**
* Get delegation time remaining
*/
getDelegationTimeRemaining(): number {
// Only check the currently connected wallet type
const activeWalletType = this.getActiveWalletType();
if (!activeWalletType) return 0;
const status = this.walletService.getDelegationStatus(activeWalletType);
return status.timeRemaining || 0;
}
/**
* Get current wallet info
*/

View File

@ -1,22 +1,57 @@
/**
* Key delegation for Bitcoin wallets
* CryptoService - Unified cryptographic operations
*
* This module handles the creation of browser-based keypairs and
* delegation of signing authority from Bitcoin wallets to these keypairs.
* Combines key delegation and message signing functionality into a single,
* cohesive service focused on all cryptographic operations.
*/
import * as ed from '@noble/ed25519';
import { sha512 } from '@noble/hashes/sha512';
import { bytesToHex, hexToBytes } from '@/lib/utils';
import { LOCAL_STORAGE_KEYS } from '@/lib/waku/constants';
import { DelegationInfo } from './types';
import { OpchanMessage } from '@/types/forum';
export interface DelegationSignature {
signature: string; // Signature from wallet
expiryTimestamp: number; // When this delegation expires
browserPublicKey: string; // Browser-generated public key that was delegated to
walletAddress: string; // Wallet address that signed the delegation
walletType: 'bitcoin' | 'ethereum'; // Type of wallet that created the delegation
}
export interface DelegationInfo extends DelegationSignature {
browserPrivateKey: string;
}
ed.etc.sha512Sync = (...m) => sha512(ed.etc.concatBytes(...m));
export type DelegationDuration = '7days' | '30days';
export class KeyDelegation {
private static readonly DEFAULT_EXPIRY_HOURS = 24;
export interface CryptoServiceInterface {
// Delegation management
createDelegation(
walletAddress: string,
signature: string,
browserPublicKey: string,
browserPrivateKey: string,
duration: DelegationDuration,
walletType: 'bitcoin' | 'ethereum'
): void;
isDelegationValid(currentAddress?: string, currentWalletType?: 'bitcoin' | 'ethereum'): boolean;
getDelegationTimeRemaining(): number;
getBrowserPublicKey(): string | null;
clearDelegation(): void;
// Keypair generation
generateKeypair(): { publicKey: string; privateKey: string };
createDelegationMessage(browserPublicKey: string, walletAddress: string, expiryTimestamp: number): string;
// Message operations
signMessage<T extends OpchanMessage>(message: T): T | null;
verifyMessage(message: OpchanMessage): boolean;
}
export class CryptoService implements CryptoServiceInterface {
private static readonly STORAGE_KEY = LOCAL_STORAGE_KEYS.KEY_DELEGATION;
// Duration options in hours
@ -24,24 +59,27 @@ export class KeyDelegation {
'7days': 24 * 7, // 168 hours
'30days': 24 * 30 // 720 hours
} as const;
/**
* Get the number of hours for a given duration
*/
static getDurationHours(duration: DelegationDuration): number {
return KeyDelegation.DURATION_HOURS[duration];
return CryptoService.DURATION_HOURS[duration];
}
/**
* Get available duration options
*/
static getAvailableDurations(): DelegationDuration[] {
return Object.keys(KeyDelegation.DURATION_HOURS) as DelegationDuration[];
return Object.keys(CryptoService.DURATION_HOURS) as DelegationDuration[];
}
// ============================================================================
// KEYPAIR GENERATION
// ============================================================================
/**
* Generates a new browser-based keypair for signing messages
* @returns Promise with keypair object containing hex-encoded public and private keys
*/
generateKeypair(): { publicKey: string; privateKey: string } {
const privateKey = ed.utils.randomPrivateKey();
@ -55,13 +93,9 @@ export class KeyDelegation {
publicKey: publicKeyHex
};
}
/**
* Creates a delegation message to be signed by the wallet
* @param browserPublicKey The browser-generated public key
* @param walletAddress The user's wallet address
* @param expiryTimestamp When the delegation will expire
* @returns The message to be signed
*/
createDelegationMessage(
browserPublicKey: string,
@ -71,15 +105,12 @@ export class KeyDelegation {
return `I, ${walletAddress}, delegate authority to this pubkey: ${browserPublicKey} until ${expiryTimestamp}`;
}
// ============================================================================
// DELEGATION MANAGEMENT
// ============================================================================
/**
* Creates a delegation object from the signed message
* @param walletAddress The wallet address that signed the delegation
* @param signature The signature from the wallet
* @param browserPublicKey The browser-generated public key
* @param browserPrivateKey The browser-generated private key
* @param duration The duration of the delegation ('1week' or '30days')
* @param walletType The type of wallet (bitcoin or ethereum)
* @returns DelegationInfo object
* Creates and stores a delegation
*/
createDelegation(
walletAddress: string,
@ -88,11 +119,11 @@ export class KeyDelegation {
browserPrivateKey: string,
duration: DelegationDuration = '7days',
walletType: 'bitcoin' | 'ethereum'
): DelegationInfo {
const expiryHours = KeyDelegation.getDurationHours(duration);
): void {
const expiryHours = CryptoService.getDurationHours(duration);
const expiryTimestamp = Date.now() + (expiryHours * 60 * 60 * 1000);
return {
const delegationInfo: DelegationInfo = {
signature,
expiryTimestamp,
browserPublicKey,
@ -100,22 +131,15 @@ export class KeyDelegation {
walletAddress,
walletType
};
localStorage.setItem(CryptoService.STORAGE_KEY, JSON.stringify(delegationInfo));
}
/**
* Stores delegation information in local storage
* @param delegationInfo The delegation information to store
*/
storeDelegation(delegationInfo: DelegationInfo): void {
localStorage.setItem(KeyDelegation.STORAGE_KEY, JSON.stringify(delegationInfo));
}
/**
* Retrieves delegation information from local storage
* @returns The stored delegation information or null if not found
*/
retrieveDelegation(): DelegationInfo | null {
const delegationJson = localStorage.getItem(KeyDelegation.STORAGE_KEY);
private retrieveDelegation(): DelegationInfo | null {
const delegationJson = localStorage.getItem(CryptoService.STORAGE_KEY);
if (!delegationJson) return null;
try {
@ -125,12 +149,9 @@ export class KeyDelegation {
return null;
}
}
/**
* Checks if a delegation is valid (exists, not expired, and matches current wallet)
* @param currentAddress Optional current wallet address to validate against
* @param currentWalletType Optional current wallet type to validate against
* @returns boolean indicating if the delegation is valid
* Checks if a delegation is valid
*/
isDelegationValid(currentAddress?: string, currentWalletType?: 'bitcoin' | 'ethereum'): boolean {
const delegation = this.retrieveDelegation();
@ -152,13 +173,42 @@ export class KeyDelegation {
return true;
}
/**
* Signs a message using the browser-generated private key
* @param message The message to sign
* @returns Promise resolving to the signature as a hex string, or null if no valid delegation
* Gets the time remaining on the current delegation
*/
signMessage(message: string): string | null {
getDelegationTimeRemaining(): number {
const delegation = this.retrieveDelegation();
if (!delegation) return 0;
const now = Date.now();
return Math.max(0, delegation.expiryTimestamp - now);
}
/**
* Gets the browser public key from the current delegation
*/
getBrowserPublicKey(): string | null {
const delegation = this.retrieveDelegation();
if (!delegation) return null;
return delegation.browserPublicKey;
}
/**
* Clears the stored delegation
*/
clearDelegation(): void {
localStorage.removeItem(CryptoService.STORAGE_KEY);
}
// ============================================================================
// MESSAGE SIGNING & VERIFICATION
// ============================================================================
/**
* Signs a raw string message using the browser-generated private key
*/
signRawMessage(message: string): string | null {
const delegation = this.retrieveDelegation();
if (!delegation || !this.isDelegationValid()) return null;
@ -166,22 +216,18 @@ export class KeyDelegation {
const privateKeyBytes = hexToBytes(delegation.browserPrivateKey);
const messageBytes = new TextEncoder().encode(message);
const signature = ed.sign(messageBytes, privateKeyBytes);
const signature = ed.sign(messageBytes, privateKeyBytes);
return bytesToHex(signature);
} catch (error) {
console.error('Error signing with browser key:', error);
return null;
}
}
/**
* Verifies a signature made with the browser key
* @param message The original message
* @param signature The signature to verify (hex string)
* @param publicKey The public key to verify against (hex string)
* @returns Promise resolving to a boolean indicating if the signature is valid
*/
verifySignature(
private verifyRawSignature(
message: string,
signature: string,
publicKey: string
@ -197,43 +243,66 @@ export class KeyDelegation {
return false;
}
}
/**
* Gets the current delegation's Bitcoin address, if available
* @returns The Bitcoin address or null if no valid delegation exists
* Signs an OpchanMessage with the delegated browser key
*/
getDelegatingAddress(): string | null {
const delegation = this.retrieveDelegation();
if (!delegation || !this.isDelegationValid()) return null;
return delegation.walletAddress;
}
/**
* Gets the browser public key from the current delegation
* @returns The browser public key or null if no valid delegation exists
*/
getBrowserPublicKey(): string | null {
signMessage<T extends OpchanMessage>(message: T): T | null {
if (!this.isDelegationValid()) {
console.error('No valid key delegation found. Cannot sign message.');
return null;
}
const delegation = this.retrieveDelegation();
if (!delegation) return null;
return delegation.browserPublicKey;
}
/**
* Clears the stored delegation
*/
clearDelegation(): void {
localStorage.removeItem(KeyDelegation.STORAGE_KEY);
}
/**
* Gets the time remaining on the current delegation
* @returns Time remaining in milliseconds, or 0 if expired/no delegation
*/
getDelegationTimeRemaining(): number {
const delegation = this.retrieveDelegation();
if (!delegation) return 0;
const now = Date.now();
return Math.max(0, delegation.expiryTimestamp - now);
// Create the message content to sign (without signature fields)
const messageToSign = JSON.stringify({
...message,
signature: undefined,
browserPubKey: undefined
});
const signature = this.signRawMessage(messageToSign);
if (!signature) return null;
return {
...message,
signature,
browserPubKey: delegation.browserPublicKey
};
}
}
/**
* Verifies an OpchanMessage signature
*/
verifyMessage(message: OpchanMessage): boolean {
// Check for required signature fields
if (!message.signature || !message.browserPubKey) {
const messageId = 'id' in message ? message.id : `${message.type}-${message.timestamp}`;
console.warn('Message is missing signature information', messageId);
return false;
}
// Reconstruct the original signed content
const signedContent = JSON.stringify({
...message,
signature: undefined,
browserPubKey: undefined
});
// Verify the signature
const isValid = this.verifyRawSignature(
signedContent,
message.signature,
message.browserPubKey
);
if (!isValid) {
const messageId = 'id' in message ? message.id : `${message.type}-${message.timestamp}`;
console.warn(`Invalid signature for message ${messageId}`);
}
return isValid;
}
}

View File

@ -1,5 +1,6 @@
import { OpchanMessage } from '@/types';
import { OpchanMessage } from '@/types/forum';
import { AuthService } from './AuthService';
import { CryptoService } from './CryptoService';
import messageManager from '@/lib/waku';
export interface MessageResult {
@ -8,23 +9,30 @@ export interface MessageResult {
error?: string;
}
export class MessageService {
private authService: AuthService;
export interface MessageServiceInterface {
sendMessage(message: OpchanMessage): Promise<MessageResult>;
verifyMessage(message: OpchanMessage): boolean;
}
constructor(authService: AuthService) {
export class MessageService implements MessageServiceInterface {
private authService: AuthService;
private cryptoService: CryptoService;
constructor(authService: AuthService, cryptoService: CryptoService) {
this.authService = authService;
this.cryptoService = cryptoService;
}
/**
* Sign and send a message to the Waku network
*/
async signAndSendMessage(message: OpchanMessage): Promise<MessageResult> {
async sendMessage(message: OpchanMessage): Promise<MessageResult> {
try {
const signedMessage = await this.authService.signMessage(message);
const signedMessage = this.cryptoService.signMessage(message);
if (!signedMessage) {
// Check if delegation exists but is expired
const isDelegationExpired = this.authService.isDelegationValid() === false;
const isDelegationExpired = this.cryptoService.isDelegationValid() === false;
return {
success: false,
@ -66,6 +74,6 @@ export class MessageService {
* Verify a message signature
*/
verifyMessage(message: OpchanMessage): boolean {
return this.authService.verifyMessage(message);
return this.cryptoService.verifyMessage(message);
}
}
}

View File

@ -1,2 +1,3 @@
export { AuthService } from './AuthService';
export { MessageService } from './MessageService';
export { AuthService, type AuthServiceInterface } from './AuthService';
export { MessageService, type MessageServiceInterface } from './MessageService';
export { CryptoService, type CryptoServiceInterface, type DelegationDuration } from './CryptoService';

View File

@ -1,71 +0,0 @@
import { OpchanMessage } from '@/types';
import { KeyDelegation } from './key-delegation';
export class MessageSigning {
private keyDelegation: KeyDelegation;
constructor(keyDelegation: KeyDelegation) {
this.keyDelegation = keyDelegation;
}
signMessage<T extends OpchanMessage>(message: T): T | null {
if (!this.keyDelegation.isDelegationValid()) {
console.error('No valid key delegation found. Cannot sign message.');
return null;
}
const delegation = this.keyDelegation.retrieveDelegation();
if (!delegation) return null;
const messageToSign = JSON.stringify({
...message,
signature: undefined,
browserPubKey: undefined
});
const signature = this.keyDelegation.signMessage(messageToSign);
if (!signature) return null;
return {
...message,
signature,
browserPubKey: delegation.browserPublicKey
};
}
verifyMessage(message: OpchanMessage): boolean {
// Check for required signature fields
if (!message.signature || !message.browserPubKey) {
const messageId = 'id' in message ? message.id : `${message.type}-${message.timestamp}`;
console.warn('Message is missing signature information', messageId);
return false;
}
// Reconstruct the original signed content
const signedContent = JSON.stringify({
...message,
signature: undefined,
browserPubKey: undefined
});
// Verify the signature
const isValid = this.keyDelegation.verifySignature(
signedContent,
message.signature,
message.browserPubKey
);
if (!isValid) {
const messageId = 'id' in message ? message.id : `${message.type}-${message.timestamp}`;
console.warn(`Invalid signature for message ${messageId}`);
}
return isValid;
}
}

View File

@ -1,11 +0,0 @@
export interface DelegationSignature {
signature: string; // Signature from wallet
expiryTimestamp: number; // When this delegation expires
browserPublicKey: string; // Browser-generated public key that was delegated to
walletAddress: string; // Wallet address that signed the delegation
walletType: 'bitcoin' | 'ethereum'; // Type of wallet that created the delegation
}
export interface DelegationInfo extends DelegationSignature {
browserPrivateKey: string;
}

View File

@ -1,5 +1,5 @@
import { UseAppKitAccountReturn } from '@reown/appkit/react';
import { KeyDelegation, DelegationDuration } from '../signatures/key-delegation';
import { CryptoService, DelegationDuration } from '../services/CryptoService';
import { AppKit } from '@reown/appkit';
import { getEnsName } from '@wagmi/core';
import { ChainNamespace } from '@reown/appkit-common';
@ -14,13 +14,13 @@ export interface WalletInfo {
}
export class ReOwnWalletService {
private keyDelegation: KeyDelegation;
private cryptoService: CryptoService;
private bitcoinAccount?: UseAppKitAccountReturn;
private ethereumAccount?: UseAppKitAccountReturn;
private appKit?: AppKit;
constructor() {
this.keyDelegation = new KeyDelegation();
this.cryptoService = new CryptoService();
}
/**
@ -130,12 +130,12 @@ export class ReOwnWalletService {
}
// Generate a new browser keypair
const keypair = this.keyDelegation.generateKeypair();
const keypair = this.cryptoService.generateKeypair();
// Create delegation message with expiry
const expiryHours = KeyDelegation.getDurationHours(duration);
const expiryHours = CryptoService.getDurationHours(duration);
const expiryTimestamp = Date.now() + (expiryHours * 60 * 60 * 1000);
const delegationMessage = this.keyDelegation.createDelegationMessage(
const delegationMessage = this.cryptoService.createDelegationMessage(
keypair.publicKey,
account.address,
expiryTimestamp
@ -147,7 +147,7 @@ export class ReOwnWalletService {
const signature = await this.signMessage(messageBytes, walletType);
// Create and store the delegation
const delegationInfo = this.keyDelegation.createDelegation(
this.cryptoService.createDelegation(
account.address,
signature,
keypair.publicKey,
@ -155,8 +155,6 @@ export class ReOwnWalletService {
duration,
walletType
);
this.keyDelegation.storeDelegation(delegationInfo);
return true;
} catch (error) {
@ -175,10 +173,10 @@ export class ReOwnWalletService {
}
// Check if we have a valid delegation for this specific wallet
if (this.keyDelegation.isDelegationValid(account.address, walletType)) {
if (this.cryptoService.isDelegationValid(account.address, walletType)) {
// Use delegated key for signing
const messageString = new TextDecoder().decode(messageBytes);
const signature = this.keyDelegation.signMessage(messageString);
const signature = this.cryptoService.signRawMessage(messageString);
if (signature) {
return signature;
@ -200,9 +198,9 @@ export class ReOwnWalletService {
const account = this.getActiveAccount(walletType);
const currentAddress = account?.address;
const hasDelegation = this.keyDelegation.retrieveDelegation() !== null;
const isValid = this.keyDelegation.isDelegationValid(currentAddress, walletType);
const timeRemaining = this.keyDelegation.getDelegationTimeRemaining();
const hasDelegation = this.cryptoService.getBrowserPublicKey() !== null;
const isValid = this.cryptoService.isDelegationValid(currentAddress, walletType);
const timeRemaining = this.cryptoService.getDelegationTimeRemaining();
return {
hasDelegation,
@ -215,7 +213,7 @@ export class ReOwnWalletService {
* Clear delegation for the connected wallet
*/
clearDelegation(walletType: 'bitcoin' | 'ethereum'): void {
this.keyDelegation.clearDelegation();
this.cryptoService.clearDelegation();
}
/**

View File

@ -14,7 +14,7 @@ export interface User {
ensAvatar?: string;
ensOwnership?: boolean;
verificationStatus: 'unverified' | 'verified-none' | 'verified-basic' | 'verified-owner' | 'verifying';
verificationStatus: EVerificationStatus;
signature?: string;
lastChecked?: number;
@ -23,6 +23,14 @@ export interface User {
delegationExpiry?: number; // When the delegation expires
}
export enum EVerificationStatus {
UNVERIFIED = 'unverified',
VERIFIED_NONE = 'verified-none',
VERIFIED_BASIC = 'verified-basic',
VERIFIED_OWNER = 'verified-owner',
VERIFYING = 'verifying',
}
export interface Cell {
id: string;
name: string;