chore: verify incoming events

This commit is contained in:
Ricardo Arturo Cabral Mejia 2022-08-07 22:47:59 +00:00
parent cce725774c
commit b2a729c926
No known key found for this signature in database
GPG Key ID: 5931EBF43A650245
12 changed files with 89 additions and 23 deletions

View File

@ -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 }]
},
};

View File

@ -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:
```

View File

@ -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: {

14
package-lock.json generated
View File

@ -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",

View File

@ -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",

View File

@ -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) {

View File

@ -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<boolean> => {
return secp256k1.schnorr.verify(event.sig, event.id, event.pubkey)
}

View File

@ -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<boolean> {
// 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
}

View File

@ -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'

View File

@ -6,7 +6,3 @@ export interface IMessageHandler {
canHandleMessageType(messageType: MessageType): boolean
handleMessage(message: Message, client: WebSocket): Promise<boolean>
}
export interface IMessageProcessor {
process(message: Message, client: WebSocket): Promise<void>
}

View File

@ -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
})
})