mirror of
https://github.com/logos-messaging/OpChan.git
synced 2026-01-07 15:23:05 +00:00
feat signature and delegation verification
This commit is contained in:
parent
6f7cbb4b45
commit
1dfd790b11
103
package-lock.json
generated
103
package-lock.json
generated
@ -11,6 +11,7 @@
|
||||
"@hookform/resolvers": "^3.9.0",
|
||||
"@noble/ed25519": "^2.2.3",
|
||||
"@noble/hashes": "^1.8.0",
|
||||
"@noble/secp256k1": "^2.3.0",
|
||||
"@radix-ui/react-accordion": "^1.2.0",
|
||||
"@radix-ui/react-alert-dialog": "^1.1.1",
|
||||
"@radix-ui/react-aspect-ratio": "^1.1.0",
|
||||
@ -64,6 +65,7 @@
|
||||
"tailwindcss-animate": "^1.0.7",
|
||||
"uuid": "^11.1.0",
|
||||
"vaul": "^0.9.3",
|
||||
"viem": "^2.34.0",
|
||||
"wagmi": "^2.16.1",
|
||||
"zod": "^3.23.8"
|
||||
},
|
||||
@ -2129,16 +2131,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@noble/secp256k1": {
|
||||
"version": "1.7.2",
|
||||
"resolved": "https://registry.npmjs.org/@noble/secp256k1/-/secp256k1-1.7.2.tgz",
|
||||
"integrity": "sha512-/qzwYl5eFLH8OWIecQWM31qld2g1NfjgylK+TNhqtaUKP37Nm+Y+z30Fjhw0Ct8p9yCQEm2N3W/AckdIb3SMcQ==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "individual",
|
||||
"url": "https://paulmillr.com/funding/"
|
||||
}
|
||||
],
|
||||
"license": "MIT"
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@noble/secp256k1/-/secp256k1-2.3.0.tgz",
|
||||
"integrity": "sha512-0TQed2gcBbIrh7Ccyw+y/uZQvbJwm7Ao4scBUxqpBCcsOlZG0O4KGfjtNAy/li4W8n1xt3dxrwJ0beZ2h2G6Kw==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"url": "https://paulmillr.com/funding/"
|
||||
}
|
||||
},
|
||||
"node_modules/@nodelib/fs.scandir": {
|
||||
"version": "2.1.5",
|
||||
@ -5564,6 +5563,18 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@waku/enr/node_modules/@noble/secp256k1": {
|
||||
"version": "1.7.2",
|
||||
"resolved": "https://registry.npmjs.org/@noble/secp256k1/-/secp256k1-1.7.2.tgz",
|
||||
"integrity": "sha512-/qzwYl5eFLH8OWIecQWM31qld2g1NfjgylK+TNhqtaUKP37Nm+Y+z30Fjhw0Ct8p9yCQEm2N3W/AckdIb3SMcQ==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "individual",
|
||||
"url": "https://paulmillr.com/funding/"
|
||||
}
|
||||
],
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@waku/interfaces": {
|
||||
"version": "0.0.29",
|
||||
"resolved": "https://registry.npmjs.org/@waku/interfaces/-/interfaces-0.0.29.tgz",
|
||||
@ -12069,6 +12080,18 @@
|
||||
"base64-js": "^1.5.1"
|
||||
}
|
||||
},
|
||||
"node_modules/jsontokens/node_modules/@noble/secp256k1": {
|
||||
"version": "1.7.2",
|
||||
"resolved": "https://registry.npmjs.org/@noble/secp256k1/-/secp256k1-1.7.2.tgz",
|
||||
"integrity": "sha512-/qzwYl5eFLH8OWIecQWM31qld2g1NfjgylK+TNhqtaUKP37Nm+Y+z30Fjhw0Ct8p9yCQEm2N3W/AckdIb3SMcQ==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "individual",
|
||||
"url": "https://paulmillr.com/funding/"
|
||||
}
|
||||
],
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/keccak": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/keccak/-/keccak-3.0.4.tgz",
|
||||
@ -15628,9 +15651,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/viem": {
|
||||
"version": "2.33.2",
|
||||
"resolved": "https://registry.npmjs.org/viem/-/viem-2.33.2.tgz",
|
||||
"integrity": "sha512-/720OaM4dHWs8vXwNpyet+PRERhPaW+n/1UVSCzyb9jkmwwVfaiy/R6YfCFb4v+XXbo8s3Fapa3DM5yCRSkulA==",
|
||||
"version": "2.34.0",
|
||||
"resolved": "https://registry.npmjs.org/viem/-/viem-2.34.0.tgz",
|
||||
"integrity": "sha512-HJZG9Wt0DLX042MG0PK17tpataxtdAEhpta9/Q44FqKwy3xZMI5Lx4jF+zZPuXFuYjZ68R0PXqRwlswHs6r4gA==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
@ -15639,14 +15662,14 @@
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@noble/curves": "1.9.2",
|
||||
"@noble/curves": "1.9.6",
|
||||
"@noble/hashes": "1.8.0",
|
||||
"@scure/bip32": "1.7.0",
|
||||
"@scure/bip39": "1.6.0",
|
||||
"abitype": "1.0.8",
|
||||
"isows": "1.0.7",
|
||||
"ox": "0.8.6",
|
||||
"ws": "8.18.2"
|
||||
"ox": "0.8.7",
|
||||
"ws": "8.18.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typescript": ">=5.0.4"
|
||||
@ -15669,6 +15692,21 @@
|
||||
"url": "https://paulmillr.com/funding/"
|
||||
}
|
||||
},
|
||||
"node_modules/viem/node_modules/@noble/curves": {
|
||||
"version": "1.9.6",
|
||||
"resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.9.6.tgz",
|
||||
"integrity": "sha512-GIKz/j99FRthB8icyJQA51E8Uk5hXmdyThjgQXRKiv9h0zeRlzSCLIzFw6K1LotZ3XuB7yzlf76qk7uBmTdFqA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@noble/hashes": "1.8.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^14.21.3 || >=16"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://paulmillr.com/funding/"
|
||||
}
|
||||
},
|
||||
"node_modules/viem/node_modules/@scure/bip32": {
|
||||
"version": "1.7.0",
|
||||
"resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-1.7.0.tgz",
|
||||
@ -15703,9 +15741,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/viem/node_modules/ox": {
|
||||
"version": "0.8.6",
|
||||
"resolved": "https://registry.npmjs.org/ox/-/ox-0.8.6.tgz",
|
||||
"integrity": "sha512-eiKcgiVVEGDtEpEdFi1EGoVVI48j6icXHce9nFwCNM7CKG3uoCXKdr4TPhS00Iy1TR2aWSF1ltPD0x/YgqIL9w==",
|
||||
"version": "0.8.7",
|
||||
"resolved": "https://registry.npmjs.org/ox/-/ox-0.8.7.tgz",
|
||||
"integrity": "sha512-W1f0FiMf9NZqtHPEDEAEkyzZDwbIKfmH2qmQx8NNiQ/9JhxrSblmtLJsSfTtQG5YKowLOnBlLVguCyxm/7ztxw==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
@ -15732,27 +15770,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/viem/node_modules/ws": {
|
||||
"version": "8.18.2",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-8.18.2.tgz",
|
||||
"integrity": "sha512-DMricUmwGZUVr++AEAe2uiVM7UoO9MAVZMDu05UQOaUII0lp+zOzLLU4Xqh/JvTqklB1T4uELaaPBKyjE1r4fQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"bufferutil": "^4.0.1",
|
||||
"utf-8-validate": ">=5.0.2"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"bufferutil": {
|
||||
"optional": true
|
||||
},
|
||||
"utf-8-validate": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/vite": {
|
||||
"version": "5.4.10",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-5.4.10.tgz",
|
||||
@ -16327,9 +16344,9 @@
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/ws": {
|
||||
"version": "8.18.1",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-8.18.1.tgz",
|
||||
"integrity": "sha512-RKW2aJZMXeMxVpnZ6bck+RswznaxmzdULiBr6KY7XkTnW8uvt0iT9H5DkHUChXrc+uurzwa0rVI16n/Xzjdz1w==",
|
||||
"version": "8.18.3",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz",
|
||||
"integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
|
||||
@ -16,6 +16,7 @@
|
||||
"@hookform/resolvers": "^3.9.0",
|
||||
"@noble/ed25519": "^2.2.3",
|
||||
"@noble/hashes": "^1.8.0",
|
||||
"@noble/secp256k1": "^2.3.0",
|
||||
"@radix-ui/react-accordion": "^1.2.0",
|
||||
"@radix-ui/react-alert-dialog": "^1.1.1",
|
||||
"@radix-ui/react-aspect-ratio": "^1.1.0",
|
||||
@ -69,6 +70,7 @@
|
||||
"tailwindcss-animate": "^1.0.7",
|
||||
"uuid": "^11.1.0",
|
||||
"vaul": "^0.9.3",
|
||||
"viem": "^2.34.0",
|
||||
"wagmi": "^2.16.1",
|
||||
"zod": "^3.23.8"
|
||||
},
|
||||
|
||||
@ -21,7 +21,7 @@ interface AuthContextType {
|
||||
delegationTimeRemaining: () => number;
|
||||
clearDelegation: () => void;
|
||||
signMessage: (message: OpchanMessage) => Promise<OpchanMessage | null>;
|
||||
verifyMessage: (message: OpchanMessage) => boolean;
|
||||
verifyMessage: (message: OpchanMessage) => Promise<boolean>;
|
||||
}
|
||||
|
||||
const AuthContext = createContext<AuthContextType | undefined>(undefined);
|
||||
@ -305,7 +305,7 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
|
||||
signMessage: async (message: OpchanMessage): Promise<OpchanMessage | null> => {
|
||||
return authServiceRef.current.signMessage(message);
|
||||
},
|
||||
verifyMessage: (message: OpchanMessage): boolean => {
|
||||
verifyMessage: async (message: OpchanMessage): Promise<boolean> => {
|
||||
return authServiceRef.current.verifyMessage(message);
|
||||
}
|
||||
};
|
||||
|
||||
@ -290,7 +290,7 @@ export class AuthService {
|
||||
/**
|
||||
* Verify a message signature
|
||||
*/
|
||||
verifyMessage(message: OpchanMessage): boolean {
|
||||
async verifyMessage(message: OpchanMessage): Promise<boolean> {
|
||||
return this.messageSigning.verifyMessage(message);
|
||||
}
|
||||
|
||||
|
||||
@ -65,7 +65,7 @@ export class MessageService {
|
||||
/**
|
||||
* Verify a message signature
|
||||
*/
|
||||
verifyMessage(message: OpchanMessage): boolean {
|
||||
async verifyMessage(message: OpchanMessage): Promise<boolean> {
|
||||
return this.authService.verifyMessage(message);
|
||||
}
|
||||
}
|
||||
@ -79,6 +79,7 @@ export class KeyDelegation {
|
||||
* @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)
|
||||
* @param walletPublicKey The public key of the wallet (for signature verification)
|
||||
* @returns DelegationInfo object
|
||||
*/
|
||||
createDelegation(
|
||||
@ -87,7 +88,8 @@ export class KeyDelegation {
|
||||
browserPublicKey: string,
|
||||
browserPrivateKey: string,
|
||||
duration: DelegationDuration = '7days',
|
||||
walletType: 'bitcoin' | 'ethereum'
|
||||
walletType: 'bitcoin' | 'ethereum',
|
||||
walletPublicKey?: string
|
||||
): DelegationInfo {
|
||||
const expiryHours = KeyDelegation.getDurationHours(duration);
|
||||
const expiryTimestamp = Date.now() + (expiryHours * 60 * 60 * 1000);
|
||||
@ -98,7 +100,8 @@ export class KeyDelegation {
|
||||
browserPublicKey,
|
||||
browserPrivateKey,
|
||||
walletAddress,
|
||||
walletType
|
||||
walletType,
|
||||
walletPublicKey
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
241
src/lib/identity/signatures/message-signing.test.ts
Normal file
241
src/lib/identity/signatures/message-signing.test.ts
Normal file
@ -0,0 +1,241 @@
|
||||
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
||||
import { MessageSigning } from './message-signing';
|
||||
import { KeyDelegation } from './key-delegation';
|
||||
import { MessageType, PostMessage } from '@/lib/waku/types';
|
||||
|
||||
// Mock the KeyDelegation class
|
||||
vi.mock('./key-delegation');
|
||||
|
||||
// Mock the WalletSignatureVerifier
|
||||
vi.mock('./wallet-signature-verifier', () => ({
|
||||
WalletSignatureVerifier: {
|
||||
verifyWalletSignature: vi.fn().mockReturnValue(true)
|
||||
}
|
||||
}));
|
||||
|
||||
describe('MessageSigning with Delegation Chain Verification', () => {
|
||||
let messageSigning: MessageSigning;
|
||||
let mockKeyDelegation: KeyDelegation;
|
||||
|
||||
beforeEach(() => {
|
||||
// Create a mock delegation
|
||||
const mockDelegation = {
|
||||
signature: 'mock-wallet-signature',
|
||||
expiryTimestamp: Date.now() + 24 * 60 * 60 * 1000, // 24 hours from now
|
||||
browserPublicKey: 'mock-browser-public-key',
|
||||
browserPrivateKey: 'mock-browser-private-key',
|
||||
walletAddress: 'mock-wallet-address',
|
||||
walletType: 'bitcoin' as const,
|
||||
walletPublicKey: 'mock-wallet-public-key' // Add wallet public key for verification
|
||||
};
|
||||
|
||||
// Setup mock methods
|
||||
mockKeyDelegation = {
|
||||
isDelegationValid: vi.fn().mockReturnValue(true),
|
||||
retrieveDelegation: vi.fn().mockReturnValue(mockDelegation),
|
||||
signMessage: vi.fn().mockReturnValue('mock-message-signature'),
|
||||
verifySignature: vi.fn().mockReturnValue(true),
|
||||
createDelegationMessage: vi.fn().mockReturnValue('I, mock-wallet-address, delegate authority to this pubkey: mock-browser-public-key until 1234567890'),
|
||||
getDelegationTimeRemaining: vi.fn().mockReturnValue(24 * 60 * 60 * 1000),
|
||||
clearDelegation: vi.fn(),
|
||||
getBrowserPublicKey: vi.fn().mockReturnValue('mock-browser-public-key'),
|
||||
getDelegatingAddress: vi.fn().mockReturnValue('mock-wallet-address'),
|
||||
generateKeypair: vi.fn().mockReturnValue({
|
||||
publicKey: 'mock-browser-public-key',
|
||||
privateKey: 'mock-browser-private-key'
|
||||
}),
|
||||
createDelegation: vi.fn().mockReturnValue(mockDelegation),
|
||||
storeDelegation: vi.fn()
|
||||
};
|
||||
|
||||
// Mock the KeyDelegation constructor
|
||||
vi.mocked(KeyDelegation).mockImplementation(() => mockKeyDelegation);
|
||||
|
||||
messageSigning = new MessageSigning(mockKeyDelegation);
|
||||
});
|
||||
|
||||
describe('signMessage', () => {
|
||||
it('should sign a message with delegation chain information', () => {
|
||||
const message: PostMessage = {
|
||||
type: MessageType.POST,
|
||||
timestamp: Date.now(),
|
||||
author: 'mock-wallet-address',
|
||||
id: 'test-post-1',
|
||||
cellId: 'test-cell-1',
|
||||
title: 'Test Post',
|
||||
content: 'Test content'
|
||||
};
|
||||
|
||||
const signedMessage = messageSigning.signMessage(message);
|
||||
|
||||
expect(signedMessage).not.toBeNull();
|
||||
expect(signedMessage).toHaveProperty('signature', 'mock-message-signature');
|
||||
expect(signedMessage).toHaveProperty('browserPubKey', 'mock-browser-public-key');
|
||||
expect(signedMessage).toHaveProperty('delegationSignature', 'mock-wallet-signature');
|
||||
expect(signedMessage).toHaveProperty('delegationMessage');
|
||||
expect(signedMessage).toHaveProperty('delegationExpiry');
|
||||
});
|
||||
|
||||
it('should return null when delegation is invalid', () => {
|
||||
mockKeyDelegation.isDelegationValid = vi.fn().mockReturnValue(false);
|
||||
|
||||
const message: PostMessage = {
|
||||
type: MessageType.POST,
|
||||
timestamp: Date.now(),
|
||||
author: 'mock-wallet-address',
|
||||
id: 'test-post-1',
|
||||
cellId: 'test-cell-1',
|
||||
title: 'Test Post',
|
||||
content: 'Test content'
|
||||
};
|
||||
|
||||
const signedMessage = messageSigning.signMessage(message);
|
||||
expect(signedMessage).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('verifyMessage', () => {
|
||||
it('should verify a valid message with delegation chain', async () => {
|
||||
const message: PostMessage & {
|
||||
signature: string;
|
||||
browserPubKey: string;
|
||||
delegationSignature: string;
|
||||
delegationMessage: string;
|
||||
delegationExpiry: number;
|
||||
} = {
|
||||
type: MessageType.POST,
|
||||
timestamp: Date.now(),
|
||||
author: 'mock-wallet-address',
|
||||
id: 'test-post-1',
|
||||
cellId: 'test-cell-1',
|
||||
title: 'Test Post',
|
||||
content: 'Test content',
|
||||
signature: 'mock-message-signature',
|
||||
browserPubKey: 'mock-browser-public-key',
|
||||
delegationSignature: 'mock-wallet-signature',
|
||||
delegationMessage: 'I, mock-wallet-address, delegate authority to this pubkey: mock-browser-public-key until 1234567890',
|
||||
delegationExpiry: Date.now() + 24 * 60 * 60 * 1000
|
||||
};
|
||||
|
||||
const isValid = await messageSigning.verifyMessage(message);
|
||||
expect(isValid).toBe(true);
|
||||
});
|
||||
|
||||
it('should reject message with missing signature fields', async () => {
|
||||
const message: PostMessage = {
|
||||
type: MessageType.POST,
|
||||
timestamp: Date.now(),
|
||||
author: 'mock-wallet-address',
|
||||
id: 'test-post-1',
|
||||
cellId: 'test-cell-1',
|
||||
title: 'Test Post',
|
||||
content: 'Test content'
|
||||
// Missing signature fields
|
||||
};
|
||||
|
||||
const isValid = await messageSigning.verifyMessage(message);
|
||||
expect(isValid).toBe(false);
|
||||
});
|
||||
|
||||
it('should reject message with missing delegation fields', async () => {
|
||||
const message: PostMessage & {
|
||||
signature: string;
|
||||
browserPubKey: string;
|
||||
} = {
|
||||
type: MessageType.POST,
|
||||
timestamp: Date.now(),
|
||||
author: 'mock-wallet-address',
|
||||
id: 'test-post-1',
|
||||
cellId: 'test-cell-1',
|
||||
title: 'Test Post',
|
||||
content: 'Test content',
|
||||
signature: 'mock-message-signature',
|
||||
browserPubKey: 'mock-browser-public-key'
|
||||
// Missing delegation fields
|
||||
};
|
||||
|
||||
const isValid = await messageSigning.verifyMessage(message);
|
||||
expect(isValid).toBe(false);
|
||||
});
|
||||
|
||||
it('should reject message with invalid signature', async () => {
|
||||
mockKeyDelegation.verifySignature = vi.fn().mockReturnValue(false);
|
||||
|
||||
const message: PostMessage & {
|
||||
signature: string;
|
||||
browserPubKey: string;
|
||||
delegationSignature: string;
|
||||
delegationMessage: string;
|
||||
delegationExpiry: number;
|
||||
} = {
|
||||
type: MessageType.POST,
|
||||
timestamp: Date.now(),
|
||||
author: 'mock-wallet-address',
|
||||
id: 'test-post-1',
|
||||
cellId: 'test-cell-1',
|
||||
title: 'Test Post',
|
||||
content: 'Test content',
|
||||
signature: 'invalid-signature',
|
||||
browserPubKey: 'mock-browser-public-key',
|
||||
delegationSignature: 'mock-wallet-signature',
|
||||
delegationMessage: 'I, mock-wallet-address, delegate authority to this pubkey: mock-browser-public-key until 1234567890',
|
||||
delegationExpiry: Date.now() + 24 * 60 * 60 * 1000
|
||||
};
|
||||
|
||||
const isValid = await messageSigning.verifyMessage(message);
|
||||
expect(isValid).toBe(false);
|
||||
});
|
||||
|
||||
it('should reject message with expired delegation', async () => {
|
||||
const message: PostMessage & {
|
||||
signature: string;
|
||||
browserPubKey: string;
|
||||
delegationSignature: string;
|
||||
delegationMessage: string;
|
||||
delegationExpiry: number;
|
||||
} = {
|
||||
type: MessageType.POST,
|
||||
timestamp: Date.now(),
|
||||
author: 'mock-wallet-address',
|
||||
id: 'test-post-1',
|
||||
cellId: 'test-cell-1',
|
||||
title: 'Test Post',
|
||||
content: 'Test content',
|
||||
signature: 'mock-message-signature',
|
||||
browserPubKey: 'mock-browser-public-key',
|
||||
delegationSignature: 'mock-wallet-signature',
|
||||
delegationMessage: 'I, mock-wallet-address, delegate authority to this pubkey: mock-browser-public-key until 1234567890',
|
||||
delegationExpiry: Date.now() - 24 * 60 * 60 * 1000 // Expired 24 hours ago
|
||||
};
|
||||
|
||||
const isValid = await messageSigning.verifyMessage(message);
|
||||
expect(isValid).toBe(false);
|
||||
});
|
||||
|
||||
it('should reject message with tampered delegation message', async () => {
|
||||
const message: PostMessage & {
|
||||
signature: string;
|
||||
browserPubKey: string;
|
||||
delegationSignature: string;
|
||||
delegationMessage: string;
|
||||
delegationExpiry: number;
|
||||
} = {
|
||||
type: MessageType.POST,
|
||||
timestamp: Date.now(),
|
||||
author: 'mock-wallet-address',
|
||||
id: 'test-post-1',
|
||||
cellId: 'test-cell-1',
|
||||
title: 'Test Post',
|
||||
content: 'Test content',
|
||||
signature: 'mock-message-signature',
|
||||
browserPubKey: 'mock-browser-public-key',
|
||||
delegationSignature: 'mock-wallet-signature',
|
||||
delegationMessage: 'I, attacker-address, delegate authority to this pubkey: mock-browser-public-key until 1234567890', // Tampered
|
||||
delegationExpiry: Date.now() + 24 * 60 * 60 * 1000
|
||||
};
|
||||
|
||||
const isValid = await messageSigning.verifyMessage(message);
|
||||
expect(isValid).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -1,7 +1,6 @@
|
||||
import { OpchanMessage } from '@/types';
|
||||
import { KeyDelegation } from './key-delegation';
|
||||
|
||||
|
||||
import { WalletSignatureVerifier } from './wallet-signature-verifier';
|
||||
|
||||
export class MessageSigning {
|
||||
private keyDelegation: KeyDelegation;
|
||||
@ -22,7 +21,10 @@ export class MessageSigning {
|
||||
const messageToSign = JSON.stringify({
|
||||
...message,
|
||||
signature: undefined,
|
||||
browserPubKey: undefined
|
||||
browserPubKey: undefined,
|
||||
delegationSignature: undefined,
|
||||
delegationMessage: undefined,
|
||||
delegationExpiry: undefined
|
||||
});
|
||||
|
||||
const signature = this.keyDelegation.signMessage(messageToSign);
|
||||
@ -31,11 +33,24 @@ export class MessageSigning {
|
||||
return {
|
||||
...message,
|
||||
signature,
|
||||
browserPubKey: delegation.browserPublicKey
|
||||
browserPubKey: delegation.browserPublicKey,
|
||||
delegationSignature: delegation.signature,
|
||||
delegationMessage: this.keyDelegation.createDelegationMessage(
|
||||
delegation.browserPublicKey,
|
||||
delegation.walletAddress,
|
||||
delegation.expiryTimestamp
|
||||
),
|
||||
delegationExpiry: delegation.expiryTimestamp
|
||||
};
|
||||
}
|
||||
|
||||
verifyMessage(message: OpchanMessage): boolean {
|
||||
async verifyMessage(message: OpchanMessage & {
|
||||
signature?: string;
|
||||
browserPubKey?: string;
|
||||
delegationSignature?: string;
|
||||
delegationMessage?: string;
|
||||
delegationExpiry?: number;
|
||||
}): Promise<boolean> {
|
||||
// Check for required signature fields
|
||||
if (!message.signature || !message.browserPubKey) {
|
||||
const messageId = 'id' in message ? message.id : `${message.type}-${message.timestamp}`;
|
||||
@ -43,29 +58,95 @@ export class MessageSigning {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check for required delegation fields
|
||||
if (!message.delegationSignature || !message.delegationMessage || !message.delegationExpiry) {
|
||||
const messageId = 'id' in message ? message.id : `${message.type}-${message.timestamp}`;
|
||||
console.warn('Message is missing delegation information', messageId);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// Reconstruct the original signed content
|
||||
// 1. Verify the message signature
|
||||
const signedContent = JSON.stringify({
|
||||
...message,
|
||||
signature: undefined,
|
||||
browserPubKey: undefined
|
||||
browserPubKey: undefined,
|
||||
delegationSignature: undefined,
|
||||
delegationMessage: undefined,
|
||||
delegationExpiry: undefined
|
||||
});
|
||||
|
||||
// Verify the signature
|
||||
const isValid = this.keyDelegation.verifySignature(
|
||||
const isValidMessageSignature = this.keyDelegation.verifySignature(
|
||||
signedContent,
|
||||
message.signature,
|
||||
message.browserPubKey
|
||||
);
|
||||
|
||||
if (!isValid) {
|
||||
if (!isValidMessageSignature) {
|
||||
const messageId = 'id' in message ? message.id : `${message.type}-${message.timestamp}`;
|
||||
console.warn(`Invalid signature for message ${messageId}`);
|
||||
console.warn(`Invalid message signature for message ${messageId}`);
|
||||
return false;
|
||||
}
|
||||
|
||||
return isValid;
|
||||
// 2. Verify delegation hasn't expired
|
||||
const now = Date.now();
|
||||
if (now >= message.delegationExpiry) {
|
||||
const messageId = 'id' in message ? message.id : `${message.type}-${message.timestamp}`;
|
||||
console.warn(`Delegation expired for message ${messageId}`);
|
||||
return false;
|
||||
}
|
||||
|
||||
// 3. Verify delegation message integrity
|
||||
const expectedDelegationMessage = this.keyDelegation.createDelegationMessage(
|
||||
message.browserPubKey,
|
||||
message.author,
|
||||
message.delegationExpiry
|
||||
);
|
||||
|
||||
if (message.delegationMessage !== expectedDelegationMessage) {
|
||||
const messageId = 'id' in message ? message.id : `${message.type}-${message.timestamp}`;
|
||||
console.warn(`Delegation message tampered for message ${messageId}`);
|
||||
return false;
|
||||
}
|
||||
|
||||
// 4. Verify wallet signature of delegation
|
||||
const isValidDelegationSignature = await this.verifyWalletSignature(
|
||||
message.delegationMessage,
|
||||
message.delegationSignature,
|
||||
message.author
|
||||
);
|
||||
|
||||
if (!isValidDelegationSignature) {
|
||||
const messageId = 'id' in message ? message.id : `${message.type}-${message.timestamp}`;
|
||||
console.warn(`Invalid delegation signature for message ${messageId}`);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify wallet signature of delegation message
|
||||
* Uses proper cryptographic verification based on wallet type
|
||||
*/
|
||||
private async verifyWalletSignature(
|
||||
delegationMessage: string,
|
||||
signature: string,
|
||||
walletAddress: string
|
||||
): Promise<boolean> {
|
||||
// Get the wallet type from the delegation
|
||||
const delegation = this.keyDelegation.retrieveDelegation();
|
||||
if (!delegation) {
|
||||
console.warn('No delegation found for wallet signature verification');
|
||||
return false;
|
||||
}
|
||||
|
||||
// Use the proper wallet signature verifier with public key
|
||||
return await WalletSignatureVerifier.verifyWalletSignature(
|
||||
delegationMessage,
|
||||
signature,
|
||||
walletAddress,
|
||||
delegation.walletType,
|
||||
delegation.walletPublicKey
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -4,6 +4,7 @@ export interface DelegationSignature {
|
||||
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
|
||||
walletPublicKey?: string; // Public key of the wallet (for signature verification)
|
||||
}
|
||||
|
||||
export interface DelegationInfo extends DelegationSignature {
|
||||
|
||||
@ -10,14 +10,20 @@ export enum MessageType {
|
||||
}
|
||||
|
||||
/**
|
||||
* Base interface for all message types
|
||||
* Base interface for all message types with delegation chain security
|
||||
*/
|
||||
export interface BaseMessage {
|
||||
type: MessageType;
|
||||
timestamp: number;
|
||||
author: string;
|
||||
|
||||
// Message signature verification fields
|
||||
signature?: string; // Message signature for verification
|
||||
browserPubKey?: string; // Public key that signed the message
|
||||
|
||||
delegationSignature?: string; // Original wallet signature of delegation
|
||||
delegationMessage?: string; // Original delegation message that was signed
|
||||
delegationExpiry?: number; // When the delegation expires
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -74,9 +74,3 @@ export interface Comment {
|
||||
relevanceScore?: number; // Calculated relevance score
|
||||
relevanceDetails?: RelevanceScoreDetails; // Detailed breakdown of relevance score calculation
|
||||
}
|
||||
|
||||
// Extended message types for verification
|
||||
export interface SignedMessage {
|
||||
signature?: string; // Signature of the message
|
||||
browserPubKey?: string; // Public key that signed the message
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user