mirror of
https://github.com/logos-messaging/OpChan.git
synced 2026-01-04 05:43:10 +00:00
chore: clean up services
This commit is contained in:
parent
62d6800f0b
commit
9b6a1c48d7
@ -2,7 +2,7 @@ import React from 'react';
|
|||||||
import { Button } from './button';
|
import { Button } from './button';
|
||||||
import { useAuth } from '@/contexts/useAuth';
|
import { useAuth } from '@/contexts/useAuth';
|
||||||
import { CheckCircle, AlertCircle, Trash2 } from 'lucide-react';
|
import { CheckCircle, AlertCircle, Trash2 } from 'lucide-react';
|
||||||
import { DelegationDuration } from '@/lib/identity/signatures/key-delegation';
|
import { DelegationDuration } from '@/lib/identity/services/CryptoService';
|
||||||
|
|
||||||
interface DelegationStepProps {
|
interface DelegationStepProps {
|
||||||
onComplete: () => void;
|
onComplete: () => void;
|
||||||
|
|||||||
@ -1,10 +1,9 @@
|
|||||||
import React, { createContext, useContext, useState, useEffect, useRef } from 'react';
|
import React, { createContext, useContext, useState, useEffect, useRef } from 'react';
|
||||||
import { useToast } from '@/components/ui/use-toast';
|
import { useToast } from '@/components/ui/use-toast';
|
||||||
import { User } from '@/types';
|
import { User, OpchanMessage, EVerificationStatus } from '@/types/forum';
|
||||||
import { AuthService, AuthResult } from '@/lib/identity/services/AuthService';
|
import { AuthService, CryptoService, MessageService, DelegationDuration } from '@/lib/identity/services';
|
||||||
import { OpchanMessage } from '@/types';
|
import { AuthResult } from '@/lib/identity/services/AuthService';
|
||||||
import { useAppKitAccount, useDisconnect, modal } from '@reown/appkit/react';
|
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';
|
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 activeAccount = isBitcoinConnected ? bitcoinAccount : ethereumAccount;
|
||||||
const address = activeAccount.address;
|
const address = activeAccount.address;
|
||||||
|
|
||||||
// Create ref for AuthService so it persists between renders
|
// Create service instances that persist between renders
|
||||||
const authServiceRef = useRef(new AuthService());
|
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
|
// Set AppKit instance and accounts in AuthService
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -76,7 +77,7 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
|
|||||||
const newUser: User = {
|
const newUser: User = {
|
||||||
address,
|
address,
|
||||||
walletType: isBitcoinConnected ? 'bitcoin' : 'ethereum',
|
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(),
|
lastChecked: Date.now(),
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -88,7 +89,7 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
|
|||||||
...newUser,
|
...newUser,
|
||||||
ensOwnership: true,
|
ensOwnership: true,
|
||||||
ensName: walletInfo.ensName,
|
ensName: walletInfo.ensName,
|
||||||
verificationStatus: 'verified-owner' as const,
|
verificationStatus: EVerificationStatus.VERIFIED_OWNER,
|
||||||
};
|
};
|
||||||
setCurrentUser(updatedUser);
|
setCurrentUser(updatedUser);
|
||||||
setVerificationStatus('verified-owner');
|
setVerificationStatus('verified-owner');
|
||||||
@ -296,15 +297,15 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const isDelegationValid = (): boolean => {
|
const isDelegationValid = (): boolean => {
|
||||||
return authServiceRef.current.isDelegationValid();
|
return cryptoServiceRef.current.isDelegationValid();
|
||||||
};
|
};
|
||||||
|
|
||||||
const delegationTimeRemaining = (): number => {
|
const delegationTimeRemaining = (): number => {
|
||||||
return authServiceRef.current.getDelegationTimeRemaining();
|
return cryptoServiceRef.current.getDelegationTimeRemaining();
|
||||||
};
|
};
|
||||||
|
|
||||||
const clearDelegation = (): void => {
|
const clearDelegation = (): void => {
|
||||||
authServiceRef.current.clearDelegation();
|
cryptoServiceRef.current.clearDelegation();
|
||||||
|
|
||||||
// Update the current user to remove delegation info
|
// Update the current user to remove delegation info
|
||||||
if (currentUser) {
|
if (currentUser) {
|
||||||
@ -329,10 +330,10 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
|
|||||||
|
|
||||||
const messageSigning = {
|
const messageSigning = {
|
||||||
signMessage: async (message: OpchanMessage): Promise<OpchanMessage | null> => {
|
signMessage: async (message: OpchanMessage): Promise<OpchanMessage | null> => {
|
||||||
return authServiceRef.current.signMessage(message);
|
return cryptoServiceRef.current.signMessage(message);
|
||||||
},
|
},
|
||||||
verifyMessage: (message: OpchanMessage): boolean => {
|
verifyMessage: (message: OpchanMessage): boolean => {
|
||||||
return authServiceRef.current.verifyMessage(message);
|
return cryptoServiceRef.current.verifyMessage(message);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { RelevanceCalculator } from '../relevance';
|
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 { VoteMessage, MessageType } from '@/lib/waku/types';
|
||||||
import { expect, describe, beforeEach, it } from 'vitest';
|
import { expect, describe, beforeEach, it } from 'vitest';
|
||||||
|
|
||||||
@ -58,7 +58,7 @@ describe('RelevanceCalculator', () => {
|
|||||||
const verifiedUser: User = {
|
const verifiedUser: User = {
|
||||||
address: 'user1',
|
address: 'user1',
|
||||||
walletType: 'ethereum',
|
walletType: 'ethereum',
|
||||||
verificationStatus: 'verified-owner',
|
verificationStatus: EVerificationStatus.VERIFIED_OWNER,
|
||||||
ensOwnership: true,
|
ensOwnership: true,
|
||||||
ensName: 'test.eth',
|
ensName: 'test.eth',
|
||||||
lastChecked: Date.now()
|
lastChecked: Date.now()
|
||||||
@ -72,7 +72,7 @@ describe('RelevanceCalculator', () => {
|
|||||||
const verifiedUser: User = {
|
const verifiedUser: User = {
|
||||||
address: 'user3',
|
address: 'user3',
|
||||||
walletType: 'bitcoin',
|
walletType: 'bitcoin',
|
||||||
verificationStatus: 'verified-owner',
|
verificationStatus: EVerificationStatus.VERIFIED_OWNER,
|
||||||
ordinalOwnership: true,
|
ordinalOwnership: true,
|
||||||
lastChecked: Date.now()
|
lastChecked: Date.now()
|
||||||
};
|
};
|
||||||
@ -85,7 +85,7 @@ describe('RelevanceCalculator', () => {
|
|||||||
const unverifiedUser: User = {
|
const unverifiedUser: User = {
|
||||||
address: 'user2',
|
address: 'user2',
|
||||||
walletType: 'ethereum',
|
walletType: 'ethereum',
|
||||||
verificationStatus: 'unverified',
|
verificationStatus: EVerificationStatus.UNVERIFIED,
|
||||||
ensOwnership: false,
|
ensOwnership: false,
|
||||||
lastChecked: Date.now()
|
lastChecked: Date.now()
|
||||||
};
|
};
|
||||||
@ -184,7 +184,7 @@ describe('RelevanceCalculator', () => {
|
|||||||
{
|
{
|
||||||
address: 'user1',
|
address: 'user1',
|
||||||
walletType: 'ethereum',
|
walletType: 'ethereum',
|
||||||
verificationStatus: 'verified-owner',
|
verificationStatus: EVerificationStatus.VERIFIED_OWNER,
|
||||||
ensOwnership: true,
|
ensOwnership: true,
|
||||||
ensName: 'test.eth',
|
ensName: 'test.eth',
|
||||||
lastChecked: Date.now()
|
lastChecked: Date.now()
|
||||||
@ -192,7 +192,7 @@ describe('RelevanceCalculator', () => {
|
|||||||
{
|
{
|
||||||
address: 'user2',
|
address: 'user2',
|
||||||
walletType: 'bitcoin',
|
walletType: 'bitcoin',
|
||||||
verificationStatus: 'unverified',
|
verificationStatus: EVerificationStatus.UNVERIFIED,
|
||||||
ordinalOwnership: false,
|
ordinalOwnership: false,
|
||||||
lastChecked: Date.now()
|
lastChecked: Date.now()
|
||||||
}
|
}
|
||||||
|
|||||||
@ -9,8 +9,7 @@ import {
|
|||||||
} from '@/lib/waku/types';
|
} from '@/lib/waku/types';
|
||||||
import { Cell, Comment, Post, User } from '@/types/forum';
|
import { Cell, Comment, Post, User } from '@/types/forum';
|
||||||
import { transformCell, transformComment, transformPost } from './transformers';
|
import { transformCell, transformComment, transformPost } from './transformers';
|
||||||
import { MessageService } from '@/lib/identity/services/MessageService';
|
import { MessageService, AuthService, CryptoService } from '@/lib/identity/services';
|
||||||
import { AuthService } from '@/lib/identity/services/AuthService';
|
|
||||||
|
|
||||||
type ToastFunction = (props: {
|
type ToastFunction = (props: {
|
||||||
title: string;
|
title: string;
|
||||||
@ -70,8 +69,9 @@ export const createPost = async (
|
|||||||
author: currentUser.address,
|
author: currentUser.address,
|
||||||
};
|
};
|
||||||
|
|
||||||
const messageService = new MessageService(authService!);
|
const cryptoService = new CryptoService();
|
||||||
const result = await messageService.signAndSendMessage(postMessage);
|
const messageService = new MessageService(authService!, cryptoService);
|
||||||
|
const result = await messageService.sendMessage(postMessage);
|
||||||
if (!result.success) {
|
if (!result.success) {
|
||||||
toast({
|
toast({
|
||||||
title: 'Post Failed',
|
title: 'Post Failed',
|
||||||
@ -141,8 +141,9 @@ export const createComment = async (
|
|||||||
author: currentUser.address,
|
author: currentUser.address,
|
||||||
};
|
};
|
||||||
|
|
||||||
const messageService = new MessageService(authService!);
|
const cryptoService = new CryptoService();
|
||||||
const result = await messageService.signAndSendMessage(commentMessage);
|
const messageService = new MessageService(authService!, cryptoService);
|
||||||
|
const result = await messageService.sendMessage(commentMessage);
|
||||||
if (!result.success) {
|
if (!result.success) {
|
||||||
toast({
|
toast({
|
||||||
title: 'Comment Failed',
|
title: 'Comment Failed',
|
||||||
@ -199,8 +200,9 @@ export const createCell = async (
|
|||||||
author: currentUser.address,
|
author: currentUser.address,
|
||||||
};
|
};
|
||||||
|
|
||||||
const messageService = new MessageService(authService!);
|
const cryptoService = new CryptoService();
|
||||||
const result = await messageService.signAndSendMessage(cellMessage);
|
const messageService = new MessageService(authService!, cryptoService);
|
||||||
|
const result = await messageService.sendMessage(cellMessage);
|
||||||
if (!result.success) {
|
if (!result.success) {
|
||||||
toast({
|
toast({
|
||||||
title: 'Cell Failed',
|
title: 'Cell Failed',
|
||||||
@ -275,8 +277,9 @@ export const vote = async (
|
|||||||
author: currentUser.address,
|
author: currentUser.address,
|
||||||
};
|
};
|
||||||
|
|
||||||
const messageService = new MessageService(authService!);
|
const cryptoService = new CryptoService();
|
||||||
const result = await messageService.signAndSendMessage(voteMessage);
|
const messageService = new MessageService(authService!, cryptoService);
|
||||||
|
const result = await messageService.sendMessage(voteMessage);
|
||||||
if (!result.success) {
|
if (!result.success) {
|
||||||
toast({
|
toast({
|
||||||
title: 'Vote Failed',
|
title: 'Vote Failed',
|
||||||
@ -340,8 +343,9 @@ export const moderatePost = async (
|
|||||||
timestamp: Date.now(),
|
timestamp: Date.now(),
|
||||||
author: currentUser.address,
|
author: currentUser.address,
|
||||||
};
|
};
|
||||||
const messageService = new MessageService(authService!);
|
const cryptoService = new CryptoService();
|
||||||
const result = await messageService.signAndSendMessage(modMsg);
|
const messageService = new MessageService(authService!, cryptoService);
|
||||||
|
const result = await messageService.sendMessage(modMsg);
|
||||||
if (!result.success) {
|
if (!result.success) {
|
||||||
toast({ title: 'Moderation Failed', description: result.error || 'Failed to moderate post. Please try again.', variant: 'destructive' });
|
toast({ title: 'Moderation Failed', description: result.error || 'Failed to moderate post. Please try again.', variant: 'destructive' });
|
||||||
return false;
|
return false;
|
||||||
@ -389,8 +393,9 @@ export const moderateComment = async (
|
|||||||
timestamp: Date.now(),
|
timestamp: Date.now(),
|
||||||
author: currentUser.address,
|
author: currentUser.address,
|
||||||
};
|
};
|
||||||
const messageService = new MessageService(authService!);
|
const cryptoService = new CryptoService();
|
||||||
const result = await messageService.signAndSendMessage(modMsg);
|
const messageService = new MessageService(authService!, cryptoService);
|
||||||
|
const result = await messageService.sendMessage(modMsg);
|
||||||
if (!result.success) {
|
if (!result.success) {
|
||||||
toast({ title: 'Moderation Failed', description: result.error || 'Failed to moderate comment. Please try again.', variant: 'destructive' });
|
toast({ title: 'Moderation Failed', description: result.error || 'Failed to moderate comment. Please try again.', variant: 'destructive' });
|
||||||
return false;
|
return false;
|
||||||
@ -437,8 +442,9 @@ export const moderateUser = async (
|
|||||||
signature: '',
|
signature: '',
|
||||||
browserPubKey: currentUser.browserPubKey,
|
browserPubKey: currentUser.browserPubKey,
|
||||||
};
|
};
|
||||||
const messageService = new MessageService(authService!);
|
const cryptoService = new CryptoService();
|
||||||
const result = await messageService.signAndSendMessage(modMsg);
|
const messageService = new MessageService(authService!, cryptoService);
|
||||||
|
const result = await messageService.sendMessage(modMsg);
|
||||||
if (!result.success) {
|
if (!result.success) {
|
||||||
toast({ title: 'Moderation Failed', description: result.error || 'Failed to moderate user. Please try again.', variant: 'destructive' });
|
toast({ title: 'Moderation Failed', description: result.error || 'Failed to moderate user. Please try again.', variant: 'destructive' });
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@ -1,11 +1,9 @@
|
|||||||
import { User } from '@/types';
|
|
||||||
import { WalletService } from '../wallets/index';
|
import { WalletService } from '../wallets/index';
|
||||||
import { UseAppKitAccountReturn } from '@reown/appkit/react';
|
import { UseAppKitAccountReturn } from '@reown/appkit/react';
|
||||||
import { AppKit } from '@reown/appkit';
|
import { AppKit } from '@reown/appkit';
|
||||||
import { OrdinalAPI } from '../ordinal';
|
import { OrdinalAPI } from '../ordinal';
|
||||||
import { MessageSigning } from '../signatures/message-signing';
|
import { CryptoService, DelegationDuration } from './CryptoService';
|
||||||
import { KeyDelegation, DelegationDuration } from '../signatures/key-delegation';
|
import { EVerificationStatus, User } from '@/types/forum';
|
||||||
import { OpchanMessage } from '@/types';
|
|
||||||
|
|
||||||
export interface AuthResult {
|
export interface AuthResult {
|
||||||
success: boolean;
|
success: boolean;
|
||||||
@ -13,17 +11,37 @@ export interface AuthResult {
|
|||||||
error?: string;
|
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 walletService: WalletService;
|
||||||
private ordinalApi: OrdinalAPI;
|
private ordinalApi: OrdinalAPI;
|
||||||
private messageSigning: MessageSigning;
|
private cryptoService: CryptoService;
|
||||||
private keyDelegation: KeyDelegation;
|
|
||||||
|
|
||||||
constructor() {
|
constructor(cryptoService: CryptoService) {
|
||||||
this.walletService = new WalletService();
|
this.walletService = new WalletService();
|
||||||
this.ordinalApi = new OrdinalAPI();
|
this.ordinalApi = new OrdinalAPI();
|
||||||
this.keyDelegation = new KeyDelegation();
|
this.cryptoService = cryptoService;
|
||||||
this.messageSigning = new MessageSigning(this.keyDelegation);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -98,7 +116,7 @@ export class AuthService {
|
|||||||
const user: User = {
|
const user: User = {
|
||||||
address: address,
|
address: address,
|
||||||
walletType: walletType,
|
walletType: walletType,
|
||||||
verificationStatus: 'unverified',
|
verificationStatus: EVerificationStatus.UNVERIFIED,
|
||||||
lastChecked: Date.now(),
|
lastChecked: Date.now(),
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -132,7 +150,7 @@ export class AuthService {
|
|||||||
*/
|
*/
|
||||||
async disconnectWallet(): Promise<void> {
|
async disconnectWallet(): Promise<void> {
|
||||||
// Clear any existing delegations when disconnecting
|
// Clear any existing delegations when disconnecting
|
||||||
this.keyDelegation.clearDelegation();
|
this.cryptoService.clearDelegation();
|
||||||
this.walletService.clearDelegation('bitcoin');
|
this.walletService.clearDelegation('bitcoin');
|
||||||
this.walletService.clearDelegation('ethereum');
|
this.walletService.clearDelegation('ethereum');
|
||||||
|
|
||||||
@ -140,13 +158,6 @@ export class AuthService {
|
|||||||
this.clearStoredUser();
|
this.clearStoredUser();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Clear delegation for current wallet
|
|
||||||
*/
|
|
||||||
clearDelegation(): void {
|
|
||||||
this.keyDelegation.clearDelegation();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Verify ordinal ownership for Bitcoin users or ENS ownership for Ethereum users
|
* Verify ordinal ownership for Bitcoin users or ENS ownership for Ethereum users
|
||||||
*/
|
*/
|
||||||
@ -182,7 +193,7 @@ export class AuthService {
|
|||||||
const updatedUser = {
|
const updatedUser = {
|
||||||
...user,
|
...user,
|
||||||
ordinalOwnership: hasOperators,
|
ordinalOwnership: hasOperators,
|
||||||
verificationStatus: hasOperators ? 'verified-owner' : 'verified-basic',
|
verificationStatus: hasOperators ? EVerificationStatus.VERIFIED_OWNER : EVerificationStatus.VERIFIED_BASIC,
|
||||||
lastChecked: Date.now(),
|
lastChecked: Date.now(),
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -207,7 +218,7 @@ export class AuthService {
|
|||||||
...user,
|
...user,
|
||||||
ensOwnership: hasENS,
|
ensOwnership: hasENS,
|
||||||
ensName: ensName,
|
ensName: ensName,
|
||||||
verificationStatus: hasENS ? 'verified-owner' : 'verified-basic',
|
verificationStatus: hasENS ? EVerificationStatus.VERIFIED_OWNER : EVerificationStatus.VERIFIED_BASIC,
|
||||||
lastChecked: Date.now(),
|
lastChecked: Date.now(),
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -223,7 +234,7 @@ export class AuthService {
|
|||||||
...user,
|
...user,
|
||||||
ensOwnership: false,
|
ensOwnership: false,
|
||||||
ensName: undefined,
|
ensName: undefined,
|
||||||
verificationStatus: 'verified-basic',
|
verificationStatus: EVerificationStatus.VERIFIED_BASIC,
|
||||||
lastChecked: Date.now(),
|
lastChecked: Date.now(),
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -262,7 +273,7 @@ export class AuthService {
|
|||||||
const delegationStatus = this.walletService.getDelegationStatus(walletType);
|
const delegationStatus = this.walletService.getDelegationStatus(walletType);
|
||||||
|
|
||||||
// Get the actual browser public key from the delegation
|
// Get the actual browser public key from the delegation
|
||||||
const browserPublicKey = this.keyDelegation.getBrowserPublicKey();
|
const browserPublicKey = this.cryptoService.getBrowserPublicKey();
|
||||||
|
|
||||||
const updatedUser = {
|
const updatedUser = {
|
||||||
...user,
|
...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
|
* Get current wallet info
|
||||||
*/
|
*/
|
||||||
|
|||||||
@ -1,22 +1,57 @@
|
|||||||
/**
|
/**
|
||||||
* Key delegation for Bitcoin wallets
|
* CryptoService - Unified cryptographic operations
|
||||||
*
|
*
|
||||||
* This module handles the creation of browser-based keypairs and
|
* Combines key delegation and message signing functionality into a single,
|
||||||
* delegation of signing authority from Bitcoin wallets to these keypairs.
|
* cohesive service focused on all cryptographic operations.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import * as ed from '@noble/ed25519';
|
import * as ed from '@noble/ed25519';
|
||||||
import { sha512 } from '@noble/hashes/sha512';
|
import { sha512 } from '@noble/hashes/sha512';
|
||||||
import { bytesToHex, hexToBytes } from '@/lib/utils';
|
import { bytesToHex, hexToBytes } from '@/lib/utils';
|
||||||
import { LOCAL_STORAGE_KEYS } from '@/lib/waku/constants';
|
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));
|
ed.etc.sha512Sync = (...m) => sha512(ed.etc.concatBytes(...m));
|
||||||
|
|
||||||
export type DelegationDuration = '7days' | '30days';
|
export type DelegationDuration = '7days' | '30days';
|
||||||
|
|
||||||
export class KeyDelegation {
|
export interface CryptoServiceInterface {
|
||||||
private static readonly DEFAULT_EXPIRY_HOURS = 24;
|
// 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;
|
private static readonly STORAGE_KEY = LOCAL_STORAGE_KEYS.KEY_DELEGATION;
|
||||||
|
|
||||||
// Duration options in hours
|
// Duration options in hours
|
||||||
@ -24,24 +59,27 @@ export class KeyDelegation {
|
|||||||
'7days': 24 * 7, // 168 hours
|
'7days': 24 * 7, // 168 hours
|
||||||
'30days': 24 * 30 // 720 hours
|
'30days': 24 * 30 // 720 hours
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the number of hours for a given duration
|
* Get the number of hours for a given duration
|
||||||
*/
|
*/
|
||||||
static getDurationHours(duration: DelegationDuration): number {
|
static getDurationHours(duration: DelegationDuration): number {
|
||||||
return KeyDelegation.DURATION_HOURS[duration];
|
return CryptoService.DURATION_HOURS[duration];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get available duration options
|
* Get available duration options
|
||||||
*/
|
*/
|
||||||
static getAvailableDurations(): DelegationDuration[] {
|
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
|
* 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 } {
|
generateKeypair(): { publicKey: string; privateKey: string } {
|
||||||
const privateKey = ed.utils.randomPrivateKey();
|
const privateKey = ed.utils.randomPrivateKey();
|
||||||
@ -55,13 +93,9 @@ export class KeyDelegation {
|
|||||||
publicKey: publicKeyHex
|
publicKey: publicKeyHex
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a delegation message to be signed by the wallet
|
* 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(
|
createDelegationMessage(
|
||||||
browserPublicKey: string,
|
browserPublicKey: string,
|
||||||
@ -71,15 +105,12 @@ export class KeyDelegation {
|
|||||||
return `I, ${walletAddress}, delegate authority to this pubkey: ${browserPublicKey} until ${expiryTimestamp}`;
|
return `I, ${walletAddress}, delegate authority to this pubkey: ${browserPublicKey} until ${expiryTimestamp}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// DELEGATION MANAGEMENT
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a delegation object from the signed message
|
* Creates and stores a delegation
|
||||||
* @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
|
|
||||||
*/
|
*/
|
||||||
createDelegation(
|
createDelegation(
|
||||||
walletAddress: string,
|
walletAddress: string,
|
||||||
@ -88,11 +119,11 @@ export class KeyDelegation {
|
|||||||
browserPrivateKey: string,
|
browserPrivateKey: string,
|
||||||
duration: DelegationDuration = '7days',
|
duration: DelegationDuration = '7days',
|
||||||
walletType: 'bitcoin' | 'ethereum'
|
walletType: 'bitcoin' | 'ethereum'
|
||||||
): DelegationInfo {
|
): void {
|
||||||
const expiryHours = KeyDelegation.getDurationHours(duration);
|
const expiryHours = CryptoService.getDurationHours(duration);
|
||||||
const expiryTimestamp = Date.now() + (expiryHours * 60 * 60 * 1000);
|
const expiryTimestamp = Date.now() + (expiryHours * 60 * 60 * 1000);
|
||||||
|
|
||||||
return {
|
const delegationInfo: DelegationInfo = {
|
||||||
signature,
|
signature,
|
||||||
expiryTimestamp,
|
expiryTimestamp,
|
||||||
browserPublicKey,
|
browserPublicKey,
|
||||||
@ -100,22 +131,15 @@ export class KeyDelegation {
|
|||||||
walletAddress,
|
walletAddress,
|
||||||
walletType
|
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
|
* Retrieves delegation information from local storage
|
||||||
* @returns The stored delegation information or null if not found
|
|
||||||
*/
|
*/
|
||||||
retrieveDelegation(): DelegationInfo | null {
|
private retrieveDelegation(): DelegationInfo | null {
|
||||||
const delegationJson = localStorage.getItem(KeyDelegation.STORAGE_KEY);
|
const delegationJson = localStorage.getItem(CryptoService.STORAGE_KEY);
|
||||||
if (!delegationJson) return null;
|
if (!delegationJson) return null;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -125,12 +149,9 @@ export class KeyDelegation {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if a delegation is valid (exists, not expired, and matches current wallet)
|
* Checks if a delegation is valid
|
||||||
* @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
|
|
||||||
*/
|
*/
|
||||||
isDelegationValid(currentAddress?: string, currentWalletType?: 'bitcoin' | 'ethereum'): boolean {
|
isDelegationValid(currentAddress?: string, currentWalletType?: 'bitcoin' | 'ethereum'): boolean {
|
||||||
const delegation = this.retrieveDelegation();
|
const delegation = this.retrieveDelegation();
|
||||||
@ -152,13 +173,42 @@ export class KeyDelegation {
|
|||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Signs a message using the browser-generated private key
|
* Gets the time remaining on the current delegation
|
||||||
* @param message The message to sign
|
|
||||||
* @returns Promise resolving to the signature as a hex string, or null if no valid 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();
|
const delegation = this.retrieveDelegation();
|
||||||
if (!delegation || !this.isDelegationValid()) return null;
|
if (!delegation || !this.isDelegationValid()) return null;
|
||||||
|
|
||||||
@ -166,22 +216,18 @@ export class KeyDelegation {
|
|||||||
const privateKeyBytes = hexToBytes(delegation.browserPrivateKey);
|
const privateKeyBytes = hexToBytes(delegation.browserPrivateKey);
|
||||||
const messageBytes = new TextEncoder().encode(message);
|
const messageBytes = new TextEncoder().encode(message);
|
||||||
|
|
||||||
const signature = ed.sign(messageBytes, privateKeyBytes);
|
const signature = ed.sign(messageBytes, privateKeyBytes);
|
||||||
return bytesToHex(signature);
|
return bytesToHex(signature);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error signing with browser key:', error);
|
console.error('Error signing with browser key:', error);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Verifies a signature made with the browser key
|
* 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,
|
message: string,
|
||||||
signature: string,
|
signature: string,
|
||||||
publicKey: string
|
publicKey: string
|
||||||
@ -197,43 +243,66 @@ export class KeyDelegation {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the current delegation's Bitcoin address, if available
|
* Signs an OpchanMessage with the delegated browser key
|
||||||
* @returns The Bitcoin address or null if no valid delegation exists
|
|
||||||
*/
|
*/
|
||||||
getDelegatingAddress(): string | null {
|
signMessage<T extends OpchanMessage>(message: T): T | null {
|
||||||
const delegation = this.retrieveDelegation();
|
if (!this.isDelegationValid()) {
|
||||||
if (!delegation || !this.isDelegationValid()) return null;
|
console.error('No valid key delegation found. Cannot sign message.');
|
||||||
return delegation.walletAddress;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the browser public key from the current delegation
|
|
||||||
* @returns The browser public key or null if no valid delegation exists
|
|
||||||
*/
|
|
||||||
getBrowserPublicKey(): string | null {
|
|
||||||
const delegation = this.retrieveDelegation();
|
const delegation = this.retrieveDelegation();
|
||||||
if (!delegation) return null;
|
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();
|
// Create the message content to sign (without signature fields)
|
||||||
return Math.max(0, delegation.expiryTimestamp - now);
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,5 +1,6 @@
|
|||||||
import { OpchanMessage } from '@/types';
|
import { OpchanMessage } from '@/types/forum';
|
||||||
import { AuthService } from './AuthService';
|
import { AuthService } from './AuthService';
|
||||||
|
import { CryptoService } from './CryptoService';
|
||||||
import messageManager from '@/lib/waku';
|
import messageManager from '@/lib/waku';
|
||||||
|
|
||||||
export interface MessageResult {
|
export interface MessageResult {
|
||||||
@ -8,23 +9,30 @@ export interface MessageResult {
|
|||||||
error?: string;
|
error?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class MessageService {
|
export interface MessageServiceInterface {
|
||||||
private authService: AuthService;
|
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.authService = authService;
|
||||||
|
this.cryptoService = cryptoService;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sign and send a message to the Waku network
|
* Sign and send a message to the Waku network
|
||||||
*/
|
*/
|
||||||
async signAndSendMessage(message: OpchanMessage): Promise<MessageResult> {
|
async sendMessage(message: OpchanMessage): Promise<MessageResult> {
|
||||||
try {
|
try {
|
||||||
const signedMessage = await this.authService.signMessage(message);
|
const signedMessage = this.cryptoService.signMessage(message);
|
||||||
|
|
||||||
if (!signedMessage) {
|
if (!signedMessage) {
|
||||||
// Check if delegation exists but is expired
|
// Check if delegation exists but is expired
|
||||||
const isDelegationExpired = this.authService.isDelegationValid() === false;
|
const isDelegationExpired = this.cryptoService.isDelegationValid() === false;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
@ -66,6 +74,6 @@ export class MessageService {
|
|||||||
* Verify a message signature
|
* Verify a message signature
|
||||||
*/
|
*/
|
||||||
verifyMessage(message: OpchanMessage): boolean {
|
verifyMessage(message: OpchanMessage): boolean {
|
||||||
return this.authService.verifyMessage(message);
|
return this.cryptoService.verifyMessage(message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1,2 +1,3 @@
|
|||||||
export { AuthService } from './AuthService';
|
export { AuthService, type AuthServiceInterface } from './AuthService';
|
||||||
export { MessageService } from './MessageService';
|
export { MessageService, type MessageServiceInterface } from './MessageService';
|
||||||
|
export { CryptoService, type CryptoServiceInterface, type DelegationDuration } from './CryptoService';
|
||||||
@ -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;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -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;
|
|
||||||
}
|
|
||||||
@ -1,5 +1,5 @@
|
|||||||
import { UseAppKitAccountReturn } from '@reown/appkit/react';
|
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 { AppKit } from '@reown/appkit';
|
||||||
import { getEnsName } from '@wagmi/core';
|
import { getEnsName } from '@wagmi/core';
|
||||||
import { ChainNamespace } from '@reown/appkit-common';
|
import { ChainNamespace } from '@reown/appkit-common';
|
||||||
@ -14,13 +14,13 @@ export interface WalletInfo {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class ReOwnWalletService {
|
export class ReOwnWalletService {
|
||||||
private keyDelegation: KeyDelegation;
|
private cryptoService: CryptoService;
|
||||||
private bitcoinAccount?: UseAppKitAccountReturn;
|
private bitcoinAccount?: UseAppKitAccountReturn;
|
||||||
private ethereumAccount?: UseAppKitAccountReturn;
|
private ethereumAccount?: UseAppKitAccountReturn;
|
||||||
private appKit?: AppKit;
|
private appKit?: AppKit;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.keyDelegation = new KeyDelegation();
|
this.cryptoService = new CryptoService();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -130,12 +130,12 @@ export class ReOwnWalletService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Generate a new browser keypair
|
// Generate a new browser keypair
|
||||||
const keypair = this.keyDelegation.generateKeypair();
|
const keypair = this.cryptoService.generateKeypair();
|
||||||
|
|
||||||
// Create delegation message with expiry
|
// Create delegation message with expiry
|
||||||
const expiryHours = KeyDelegation.getDurationHours(duration);
|
const expiryHours = CryptoService.getDurationHours(duration);
|
||||||
const expiryTimestamp = Date.now() + (expiryHours * 60 * 60 * 1000);
|
const expiryTimestamp = Date.now() + (expiryHours * 60 * 60 * 1000);
|
||||||
const delegationMessage = this.keyDelegation.createDelegationMessage(
|
const delegationMessage = this.cryptoService.createDelegationMessage(
|
||||||
keypair.publicKey,
|
keypair.publicKey,
|
||||||
account.address,
|
account.address,
|
||||||
expiryTimestamp
|
expiryTimestamp
|
||||||
@ -147,7 +147,7 @@ export class ReOwnWalletService {
|
|||||||
const signature = await this.signMessage(messageBytes, walletType);
|
const signature = await this.signMessage(messageBytes, walletType);
|
||||||
|
|
||||||
// Create and store the delegation
|
// Create and store the delegation
|
||||||
const delegationInfo = this.keyDelegation.createDelegation(
|
this.cryptoService.createDelegation(
|
||||||
account.address,
|
account.address,
|
||||||
signature,
|
signature,
|
||||||
keypair.publicKey,
|
keypair.publicKey,
|
||||||
@ -155,8 +155,6 @@ export class ReOwnWalletService {
|
|||||||
duration,
|
duration,
|
||||||
walletType
|
walletType
|
||||||
);
|
);
|
||||||
|
|
||||||
this.keyDelegation.storeDelegation(delegationInfo);
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -175,10 +173,10 @@ export class ReOwnWalletService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check if we have a valid delegation for this specific wallet
|
// 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
|
// Use delegated key for signing
|
||||||
const messageString = new TextDecoder().decode(messageBytes);
|
const messageString = new TextDecoder().decode(messageBytes);
|
||||||
const signature = this.keyDelegation.signMessage(messageString);
|
const signature = this.cryptoService.signRawMessage(messageString);
|
||||||
|
|
||||||
if (signature) {
|
if (signature) {
|
||||||
return signature;
|
return signature;
|
||||||
@ -200,9 +198,9 @@ export class ReOwnWalletService {
|
|||||||
const account = this.getActiveAccount(walletType);
|
const account = this.getActiveAccount(walletType);
|
||||||
const currentAddress = account?.address;
|
const currentAddress = account?.address;
|
||||||
|
|
||||||
const hasDelegation = this.keyDelegation.retrieveDelegation() !== null;
|
const hasDelegation = this.cryptoService.getBrowserPublicKey() !== null;
|
||||||
const isValid = this.keyDelegation.isDelegationValid(currentAddress, walletType);
|
const isValid = this.cryptoService.isDelegationValid(currentAddress, walletType);
|
||||||
const timeRemaining = this.keyDelegation.getDelegationTimeRemaining();
|
const timeRemaining = this.cryptoService.getDelegationTimeRemaining();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
hasDelegation,
|
hasDelegation,
|
||||||
@ -215,7 +213,7 @@ export class ReOwnWalletService {
|
|||||||
* Clear delegation for the connected wallet
|
* Clear delegation for the connected wallet
|
||||||
*/
|
*/
|
||||||
clearDelegation(walletType: 'bitcoin' | 'ethereum'): void {
|
clearDelegation(walletType: 'bitcoin' | 'ethereum'): void {
|
||||||
this.keyDelegation.clearDelegation();
|
this.cryptoService.clearDelegation();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -14,7 +14,7 @@ export interface User {
|
|||||||
ensAvatar?: string;
|
ensAvatar?: string;
|
||||||
ensOwnership?: boolean;
|
ensOwnership?: boolean;
|
||||||
|
|
||||||
verificationStatus: 'unverified' | 'verified-none' | 'verified-basic' | 'verified-owner' | 'verifying';
|
verificationStatus: EVerificationStatus;
|
||||||
|
|
||||||
signature?: string;
|
signature?: string;
|
||||||
lastChecked?: number;
|
lastChecked?: number;
|
||||||
@ -23,6 +23,14 @@ export interface User {
|
|||||||
delegationExpiry?: number; // When the delegation expires
|
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 {
|
export interface Cell {
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user