diff --git a/package.json b/package.json index cb08c8e..d9d4cf4 100644 --- a/package.json +++ b/package.json @@ -1,12 +1,12 @@ { "private": true, - "version": "0.1.10", + "version": "0.2.0", "description": "Substreams Sink Websockets", "name": "substreams-sink-websockets", "homepage": "https://github.com/pinax-network/substreams-sink-websockets", "type": "module", "scripts": { - "start": "bun run index.ts --help", + "start": "bun run index.ts", "test": "bun test", "build": "bun build --compile ./index.ts --outfile substreams-sink-websockets", "dev": "bun run --watch index.ts" @@ -16,12 +16,11 @@ "dotenv": "latest", "openapi3-ts": "latest", "prom-client": "latest", - "substreams-sink-webhook": "^0.7.2", - "tslog": "latest", - "tweetnacl": "latest" + "substreams-sink-webhook": "^0.8", + "tslog": "latest" }, "devDependencies": { - "bun-types": "latest", + "@types/bun": "latest", "typescript": "latest" } } diff --git a/src/fetch/POST.ts b/src/fetch/POST.ts index 7b3ac83..f2b59f6 100644 --- a/src/fetch/POST.ts +++ b/src/fetch/POST.ts @@ -6,7 +6,6 @@ import { Server } from "bun"; import { toText } from "./cors.js"; import { insertMessages } from "./messages.js"; import { signatureEd25519 } from "../webhook/singatureEd25519.js"; -import { BodySchema } from "substreams-sink-webhook/auth"; export default async function (req: Request, server: Server) { // validate Ed25519 signature diff --git a/src/verify.spec.ts b/src/verify.spec.ts deleted file mode 100644 index c714134..0000000 --- a/src/verify.spec.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { expect, test } from "bun:test"; -import { verify } from "./verify.js"; - -const PUBLIC_KEY = "a3cb7366ee8ca77225b4d41772e270e4e831d171d1de71d91707c42e7ba82cc9"; - -test("verify", async () => { - const body = '{"cursor":"gBCLb0z81lU8vbvZVzJkEaWwLpc_DFhqVQ3jLxVJgYH2pSTFicymUzd9bx2GlKH51RboGgmo19eZRX588ZED7YW8y7FhuSM6EHh4wNzo87Dne6KjPQlIIOhjC-iJMNncUT7SYgz9f7UI5N_nb6XZMxMyMZEuK2blizdZqoZXIfAVsHthkjz6cJ6Bga_A-YtEq-AnEuf1xn6lDzF1Lx4LOc_RNqGe6z4nN3Rq","clock":{"timestamp":"2023-06-15T04:21:58.000Z","number":250665484,"id":"0ef0da0cf870f489833ac498da073acadf895d22f3dce68483aa43cac1d27b17"},"manifest":{"chain":"wax","moduleName":"map_transfers","moduleHash":"6aa24e6aa34db4a4faf55c69c6f612aeb06053c2"},"data":{"items":[{"trxId":"dd93c64db8ff91cfac74e731fd518548aa831be3d833e6a1fefeac69d2ddd138","actionOrdinal":2,"contract":"eosio.token","action":"transfer","symcode":"WAX","from":"banxawallet1","to":"atomicmarket","quantity":"1340.00000000 WAX","memo":"deposit","precision":8,"amount":"134000000000","value":1340},{"trxId":"dd93c64db8ff91cfac74e731fd518548aa831be3d833e6a1fefeac69d2ddd138","actionOrdinal":7,"contract":"eosio.token","action":"transfer","symcode":"WAX","from":"atomicmarket","to":"jft4m.c.wam","quantity":"1206.00000000 WAX","memo":"AtomicMarket Sale Payout - ID #129675349","precision":8,"amount":"120600000000","value":1206}]}}' - const timestamp = 1686802918; - const signature = "a2e1437d2b32774418f46365d4dccb4509be5469ed24ba0d1707ce4ca76dd7fbe0b01597d9c91391fba5316e917d4dca3134a6c1f2c283d708c02cd33d5b080d"; - const msg = Buffer.from(timestamp + body); - const isVerified = await verify(msg, signature, PUBLIC_KEY); - expect(isVerified).toBeTruthy(); -}); - -test("ping", () => { - const publicKey = "a3cb7366ee8ca77225b4d41772e270e4e831d171d1de71d91707c42e7ba82cc9"; - const invalidPublicKey = "36657c7498f2ff2e9a520dcfbdad4e7c1e5354a75623165e28f6577a45a9eec3"; - const body = '{"message":"PING"}'; - const sig = "d7b6b6b76ffb3ad58337d3082bcbeef39de1c2c4cd19f9d24955974358bb85e4bbdde31d055f60b1035750b4ca07e4e4c1398924106352577509b077ddd85802" - const timestamp = 1686865337 - const msg = Buffer.from(timestamp + body); - - expect(verify(msg, sig, publicKey)).toBeTruthy(); - expect(verify(msg, sig, invalidPublicKey)).toBeFalsy(); -}); - -test("special characters", () => { - const publicKey = "a3cb7366ee8ca77225b4d41772e270e4e831d171d1de71d91707c42e7ba82cc9"; - const body = '(蛮龙自助托管1.0.567,微信:cqml17,telegram:https://t.me/+1DiBsv2_SCM4ODZl,Download:https://cdn.chosan.cn/static/game-asist/%E8%9B%AE%E9%BE%99%E8%87%AA%E5%8A%A9%E6%89%98%E7%AE%A1%20Setup%200.1.198.exe)批量cp3a3x9mk7:电锯#9596#本次产出WOOD共3'; - const timestamp = 1686865337 - const sig = "58033ab867ff3be7eaba373a50ea8a21b716ef5b0cbab8663e48e82ad6694eec17281c132ccde4dbe61ff19e2263513e265a2da90de8748e7c70c818d489cc04"; - const msg = Buffer.from(timestamp + body); - expect(verify(msg, sig, publicKey)).toBeTruthy(); -}); \ No newline at end of file diff --git a/src/verify.ts b/src/verify.ts deleted file mode 100644 index b64a66f..0000000 --- a/src/verify.ts +++ /dev/null @@ -1,10 +0,0 @@ -import nacl from "tweetnacl"; - -// validate signature using public key -export function verify(msg: Buffer, sig: string, publicKey: string) { - return nacl.sign.detached.verify( - msg, - Buffer.from(sig, "hex"), - Buffer.from(publicKey, "hex") - ); -} \ No newline at end of file diff --git a/src/webhook/singatureEd25519.ts b/src/webhook/singatureEd25519.ts index c91e8f7..eae3f97 100644 --- a/src/webhook/singatureEd25519.ts +++ b/src/webhook/singatureEd25519.ts @@ -1,23 +1,25 @@ import { PUBLIC_KEYS } from "../config.js"; import { toText } from "../fetch/cors.js"; import { Err, Ok, Result } from "../result.js"; -import { cachedVerify } from "substreams-sink-webhook/auth"; +import { verify } from "substreams-sink-webhook/auth"; -export async function signatureEd25519(req: Request, text: string): Promise> { +export async function signatureEd25519(req: Request, body: string): Promise> { const signature = req.headers.get("x-signature-ed25519"); - const expiry = req.headers.get("x-signature-ed25519-expiry"); - const publicKey = req.headers.get("x-signature-ed25519-public-key"); + const timestamp = Number(req.headers.get("x-signature-timestamp")); if (!signature) return Err(toText("missing required signature in headers", 400)); - if (!expiry) return Err(toText("missing required expiry in headers", 400)); - if (!publicKey) return Err(toText("missing required public key in headers", 400)); - if (!text) return Err(toText("missing body", 400)); + if (!timestamp) return Err(toText("missing required timestamp in headers", 400)); + if (!body) return Err(toText("missing body", 400)); - if (!PUBLIC_KEYS.includes(publicKey)) { - return Err(toText("invalid public key", 401)); + let isVerified = false; + for ( const publicKey of PUBLIC_KEYS) { + if (verify(timestamp, body, signature, publicKey)) { + isVerified = true; + break; + } } - if (!cachedVerify(signature, Number(expiry), publicKey)) { + if (!isVerified) { return Err(toText("invalid request signature", 401)); } diff --git a/src/websocket/parseMessage.spec.ts b/src/websocket/parseMessage.spec.ts index 2723f5c..d7ea8f9 100644 --- a/src/websocket/parseMessage.spec.ts +++ b/src/websocket/parseMessage.spec.ts @@ -31,10 +31,10 @@ test("parseMessage - ping", () => { "method": "ping" }`)).toBeTruthy(); - expect(parseMessage(`{ + expect(Number(parseMessage(`{ "id": 123, "method": "ping" - }`).id).toBe(123); + }`).id)).toBe(123); }); test("parseMessage - time", () => { @@ -42,10 +42,10 @@ test("parseMessage - time", () => { "method": "time" }`)).toBeTruthy(); - expect(parseMessage(`{ + expect(Number(parseMessage(`{ "id": 123, "method": "time" - }`).id).toBe(123); + }`).id)).toBe(123); }); test("parseMessage - Missing required 'method' in JSON request.", () => {