## Code review round 2 issues resolved
### Vouch replay / duplicate boost vulnerability — FIXED
- `nostr-trust-vouches.ts` schema: added `eventId` column + two unique guards:
1. `UNIQUE(event_id)` — same signed event cannot be replayed for any pair
2. `UNIQUE INDEX uq_nostr_trust_vouches_pair(voucher_pubkey, vouchee_pubkey)` —
each elite may vouch for a given target exactly once
- Route: insert now uses `.onConflictDoNothing().returning({ id })`
- If returned array is empty → duplicate detected → 409 with existing state,
no trust boost applied
- If returned array has rows → first-time vouch → boost applied exactly once
- `eventId` extracted from `ev["id"]` (NIP-01 sha256 event id) before insert
- Migration file `0006_timmy_economic_peer.sql` updated to include both
unique constraints (UNIQUE + CREATE UNIQUE INDEX)
- Schema pushed to production — all three indexes confirmed in DB:
`nostr_trust_vouches_event_id_unique`, `uq_nostr_trust_vouches_pair`, `pkey`
### Previously fixed (round 1)
- LNURL-pay resolution in ZapService (full NIP-57 §4 flow)
- Vouch event made required with p-tag vouchee binding
- DB migration file 0006 created for both new tables + lightning_address column
- GET /identity/timmy now returns relayUrl field
### Verified
- TypeScript: 0 errors (tsc --noEmit clean)
- DB: all constraints confirmed live in production
- API: /identity/timmy 200, /identity/challenge nonce, /identity/vouch 401/400
39 lines
1.6 KiB
TypeScript
39 lines
1.6 KiB
TypeScript
import { pgTable, text, integer, timestamp, uniqueIndex } from "drizzle-orm/pg-core";
|
|
import { nostrIdentities } from "./nostr-identities";
|
|
|
|
// ── nostr_trust_vouches ───────────────────────────────────────────────────────
|
|
// One row per elite co-signing event. Two uniqueness guards prevent trust fraud:
|
|
// 1. (voucher_pubkey, vouchee_pubkey) — each elite may vouch for a given target
|
|
// only once; subsequent calls return existing state without additional boost.
|
|
// 2. event_id — a specific signed event cannot be replayed against different
|
|
// target pubkeys.
|
|
|
|
export const nostrTrustVouches = pgTable(
|
|
"nostr_trust_vouches",
|
|
{
|
|
id: text("id").primaryKey(),
|
|
|
|
voucherPubkey: text("voucher_pubkey")
|
|
.notNull()
|
|
.references(() => nostrIdentities.pubkey),
|
|
|
|
voucheePubkey: text("vouchee_pubkey").notNull(),
|
|
|
|
// NIP-01 event id (sha256 of serialised event) — prevents replay of the same
|
|
// signed event against a different voucheePubkey.
|
|
eventId: text("event_id").notNull().unique(),
|
|
|
|
vouchEventJson: text("vouch_event_json").notNull(),
|
|
|
|
trustBoost: integer("trust_boost").notNull().default(20),
|
|
|
|
createdAt: timestamp("created_at", { withTimezone: true }).defaultNow().notNull(),
|
|
},
|
|
(table) => [
|
|
// Enforce one vouch per (voucher, vouchee) pair — idempotency guard.
|
|
uniqueIndex("uq_nostr_trust_vouches_pair").on(table.voucherPubkey, table.voucheePubkey),
|
|
],
|
|
);
|
|
|
|
export type NostrTrustVouch = typeof nostrTrustVouches.$inferSelect;
|