From b2a729c92651a13c21721cee087c0376f89bc531 Mon Sep 17 00:00:00 2001 From: Ricardo Arturo Cabral Mejia Date: Sun, 7 Aug 2022 22:47:59 +0000 Subject: [PATCH] chore: verify incoming events --- .eslintrc.js | 2 +- README.md | 30 +++++++++++++++++ knexfile.js | 10 +++--- package-lock.json | 14 ++++---- package.json | 2 +- src/{relay => adapters}/web-server-adapter.ts | 0 .../web-socket-server-adapter.ts | 2 +- src/event.ts | 5 +++ src/handlers/event-message-handler.ts | 8 +++-- src/index.ts | 3 +- src/types/message-handlers.ts | 4 --- test/unit/events.spec.ts | 32 ++++++++++++++++++- 12 files changed, 89 insertions(+), 23 deletions(-) rename src/{relay => adapters}/web-server-adapter.ts (100%) rename src/{relay => adapters}/web-socket-server-adapter.ts (98%) diff --git a/.eslintrc.js b/.eslintrc.js index 822577f..dcd8fc0 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -16,6 +16,6 @@ module.exports = { "@typescript-eslint/no-unused-vars": ["error", { argsIgnorePattern: "^_" }], "no-console": "off", semi: ["error", "never"], - quotes: ["error", "single"] + quotes: ["error", "single", { avoidEscape: true }] }, }; diff --git a/README.md b/README.md index 386e7c4..acfea0e 100644 --- a/README.md +++ b/README.md @@ -24,14 +24,44 @@ NIPs with a relay-specific implementation are listed here. - [ ] NIP-16: Event Treatment - [ ] NIP-25: Reactions +## Requirements + +- PostgreSQL +- Node +- Typescript + ## Quick Start +Set the following environment variables: + + ``` + DB_HOST=localhost + DB_PORT=5432 + DB_NAME=nostr-ts-relay + DB_USER=postgres + DB_PASSWORD=postgres + ``` + +Create `nostr-ts-relay` database: + + ``` + $ psql -h $DB_HOST -p $DB_PORT -U $DB_USER -W + postgres=# create database nostr-ts-relay; + postgres=# quit + ``` + Install dependencies: ``` npm install ``` +Run migrations: + + ``` + npm run db:migrate + ``` + To start in development mode: ``` diff --git a/knexfile.js b/knexfile.js index a7f0f03..0f77275 100644 --- a/knexfile.js +++ b/knexfile.js @@ -1,11 +1,11 @@ module.exports = { client: 'pg', connection: { - host: process.env.DB_HOST, - port: process.env.DB_PORT, - user: process.env.DB_USER, - password: process.env.DB_PASSWORD, - database: process.env.DB_NAME, + host: process.env.DB_HOST ?? 'localhost', + port: process.env.DB_PORT ?? 5432, + user: process.env.DB_USER ?? 'postgres', + password: process.env.DB_PASSWORD ?? 'postgres', + database: process.env.DB_NAME ?? 'nostr-ts-relay', }, pool: { min: 0, max: 7 }, seeds: { diff --git a/package-lock.json b/package-lock.json index e5c3870..e34eae9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,7 @@ "version": "1.0.0", "license": "MIT", "dependencies": { - "@noble/secp256k1": "^1.5.5", + "@noble/secp256k1": "1.6.3", "joi": "^17.6.0", "knex": "^2.0.0", "pg": "^8.7.3", @@ -112,9 +112,9 @@ "dev": true }, "node_modules/@noble/secp256k1": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/@noble/secp256k1/-/secp256k1-1.5.5.tgz", - "integrity": "sha512-sZ1W6gQzYnu45wPrWx8D3kwI2/U29VYTx9OjbDAd7jwRItJ0cSTMPRL/C8AWZFn9kWFLQGqEXVEE86w4Z8LpIQ==", + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/@noble/secp256k1/-/secp256k1-1.6.3.tgz", + "integrity": "sha512-T04e4iTurVy7I8Sw4+c5OSN9/RkPlo1uKxAomtxQNLq8j1uPAqnsqG1bqvY3Jv7c13gyr6dui0zmh/I3+f/JaQ==", "funding": [ { "type": "individual", @@ -3160,9 +3160,9 @@ "dev": true }, "@noble/secp256k1": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/@noble/secp256k1/-/secp256k1-1.5.5.tgz", - "integrity": "sha512-sZ1W6gQzYnu45wPrWx8D3kwI2/U29VYTx9OjbDAd7jwRItJ0cSTMPRL/C8AWZFn9kWFLQGqEXVEE86w4Z8LpIQ==" + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/@noble/secp256k1/-/secp256k1-1.6.3.tgz", + "integrity": "sha512-T04e4iTurVy7I8Sw4+c5OSN9/RkPlo1uKxAomtxQNLq8j1uPAqnsqG1bqvY3Jv7c13gyr6dui0zmh/I3+f/JaQ==" }, "@nodelib/fs.scandir": { "version": "2.1.5", diff --git a/package.json b/package.json index ced06a6..9688f7a 100644 --- a/package.json +++ b/package.json @@ -52,7 +52,7 @@ "uuid": "^8.3.2" }, "dependencies": { - "@noble/secp256k1": "^1.5.5", + "@noble/secp256k1": "1.6.3", "joi": "^17.6.0", "knex": "^2.0.0", "pg": "^8.7.3", diff --git a/src/relay/web-server-adapter.ts b/src/adapters/web-server-adapter.ts similarity index 100% rename from src/relay/web-server-adapter.ts rename to src/adapters/web-server-adapter.ts diff --git a/src/relay/web-socket-server-adapter.ts b/src/adapters/web-socket-server-adapter.ts similarity index 98% rename from src/relay/web-socket-server-adapter.ts rename to src/adapters/web-socket-server-adapter.ts index e8afe90..1e84637 100644 --- a/src/relay/web-socket-server-adapter.ts +++ b/src/adapters/web-socket-server-adapter.ts @@ -83,7 +83,7 @@ export class WebSocketServerAdapter extends WebServerAdapter implements IWebSock this.onWebSocketClientClose(client) }) - client.on('pong', () => this.onWebSocketClientPong.bind(this)(client)) + client.on('pong', () => this.onWebSocketClientPong.call(this, client)) } private async onWebSocketClientMessage(client: WebSocket, message: Message) { diff --git a/src/event.ts b/src/event.ts index dbd23db..dd04f0e 100644 --- a/src/event.ts +++ b/src/event.ts @@ -1,3 +1,4 @@ +import * as secp256k1 from '@noble/secp256k1' import { CanonicalEvent, Event } from './types/event' import { SubscriptionFilter } from './types/subscription' @@ -57,3 +58,7 @@ export const isEventMatchingFilter = return true } + +export const isEventSignatureValid = async (event: Event): Promise => { + return secp256k1.schnorr.verify(event.sig, event.id, event.pubkey) +} diff --git a/src/handlers/event-message-handler.ts b/src/handlers/event-message-handler.ts index be69ccc..c09032a 100644 --- a/src/handlers/event-message-handler.ts +++ b/src/handlers/event-message-handler.ts @@ -2,6 +2,7 @@ import { IMessageHandler } from '../types/message-handlers' import { MessageType, IncomingEventMessage } from '../types/messages' import { IWebSocketServerAdapter } from '../types/servers' import { IEventRepository } from '../types/repositories' +import { isEventSignatureValid } from '../event' export class EventMessageHandler implements IMessageHandler { public constructor( @@ -14,11 +15,14 @@ export class EventMessageHandler implements IMessageHandler { } public async handleMessage(message: IncomingEventMessage): Promise { - // TODO: validate + if (!await isEventSignatureValid(message[1])) { + console.warn(`Event ${message[1].id} from ${message[1].pubkey} with signature ${message[1].sig} is not valid`) + return + } + try { const count = await this.eventRepository.create(message[1]) if (!count) { - console.debug('Event already exists.') return true } diff --git a/src/index.ts b/src/index.ts index 8ec2e03..aad3f1f 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,8 +1,9 @@ import * as http from 'http' import { WebSocketServer } from 'ws' + import { getDbClient } from './database/client' import { EventRepository } from './repositories/event-repository' -import { WebSocketServerAdapter } from './relay/web-socket-server-adapter' +import { WebSocketServerAdapter } from './adapters/web-socket-server-adapter' import { SubscribeMessageHandler } from './handlers/subscribe-message-handler' import { UnsubscribeMessageHandler } from './handlers/unsubscribe-message-handler' import { EventMessageHandler } from './handlers/event-message-handler' diff --git a/src/types/message-handlers.ts b/src/types/message-handlers.ts index 4d1cad4..4176416 100644 --- a/src/types/message-handlers.ts +++ b/src/types/message-handlers.ts @@ -6,7 +6,3 @@ export interface IMessageHandler { canHandleMessageType(messageType: MessageType): boolean handleMessage(message: Message, client: WebSocket): Promise } - -export interface IMessageProcessor { - process(message: Message, client: WebSocket): Promise -} \ No newline at end of file diff --git a/test/unit/events.spec.ts b/test/unit/events.spec.ts index 95edb2b..1ad9fa9 100644 --- a/test/unit/events.spec.ts +++ b/test/unit/events.spec.ts @@ -1,6 +1,6 @@ import { expect } from 'chai' import { Event, CanonicalEvent } from '../../src/types/event' -import { isEventMatchingFilter, serializeEvent } from '../../src/event' +import { isEventMatchingFilter, isEventSignatureValid, serializeEvent } from '../../src/event' import { EventKinds } from '../../src/constants/base' describe('serializeEvent', () => { @@ -198,3 +198,33 @@ describe('isEventMatchingFilter', () => { }) }) }) + +describe('isEventSignatureValid', () => { + let event: Event + + beforeEach(() => { + event = { + 'id': 'b1601d26958e6508b7b9df0af609c652346c09392b6534d93aead9819a51b4ef', + 'pubkey': '22e804d26ed16b68db5259e78449e96dab5d464c8f470bda3eb1a70467f2c793', + 'created_at': 1648339664, + 'kind': 1, + 'tags': [], + 'content': 'learning terraform rn!', + 'sig': 'ec8b2bc640c8c7e92fbc0e0a6f539da2635068a99809186f15106174d727456132977c78f3371d0ab01c108173df75750f33d8e04c4d7980bbb3fb70ba1e3848' + } + }) + + it('resolves with true if event has a valid signature', async () => { + expect( + await isEventSignatureValid(event) + ).to.be.true + }) + + it('resolves with false if event has a valid signature', async () => { + event.id = '1234567890123456789012345678901234567890123456789012345678901234' + + expect( + await isEventSignatureValid(event) + ).to.be.false + }) +}) \ No newline at end of file