From 434809621b4fffd6505100caf4b78172973315b0 Mon Sep 17 00:00:00 2001 From: anshumancanrock Date: Sat, 23 May 2026 06:23:59 +0530 Subject: [PATCH] feat: add NIP-42 foundation types, schemas and constants --- .changeset/vast-friends-march.md | 5 ++++ src/@types/messages.ts | 16 ++++++++-- src/constants/base.ts | 5 ++++ src/schemas/message-schema.ts | 5 +++- src/utils/messages.ts | 6 ++++ test/unit/schemas/message-schema.spec.ts | 37 ++++++++++++++++++++++++ 6 files changed, 71 insertions(+), 3 deletions(-) create mode 100644 .changeset/vast-friends-march.md diff --git a/.changeset/vast-friends-march.md b/.changeset/vast-friends-march.md new file mode 100644 index 00000000..dd536abf --- /dev/null +++ b/.changeset/vast-friends-march.md @@ -0,0 +1,5 @@ +--- +"nostream": minor +--- + +feat: add NIP-42 types, schemas and constants diff --git a/src/@types/messages.ts b/src/@types/messages.ts index f95538f8..44372592 100644 --- a/src/@types/messages.ts +++ b/src/@types/messages.ts @@ -12,13 +12,14 @@ export enum MessageType { OK = 'OK', COUNT = 'COUNT', CLOSED = 'CLOSED', + AUTH = 'AUTH', } -export type IncomingMessage = (SubscribeMessage | IncomingEventMessage | UnsubscribeMessage | CountMessage) & { +export type IncomingMessage = (SubscribeMessage | IncomingEventMessage | UnsubscribeMessage | CountMessage | AuthMessage) & { [ContextMetadataKey]?: ContextMetadata } -export type OutgoingMessage = OutgoingEventMessage | EndOfStoredEventsNotice | NoticeMessage | CommandResult | CountResultMessage | ClosedMessage +export type OutgoingMessage = OutgoingEventMessage | EndOfStoredEventsNotice | NoticeMessage | CommandResult | CountResultMessage | ClosedMessage | AuthChallengeMessage export type SubscribeMessage = { [index in Range<2, 100>]: SubscriptionFilter @@ -89,3 +90,14 @@ export interface ClosedMessage { 1: SubscriptionId 2: string } + +// NIP-42 +export interface AuthMessage { + 0: MessageType.AUTH + 1: Event +} + +export interface AuthChallengeMessage { + 0: MessageType.AUTH + 1: string +} diff --git a/src/constants/base.ts b/src/constants/base.ts index 113ea76c..f37a41d8 100644 --- a/src/constants/base.ts +++ b/src/constants/base.ts @@ -45,6 +45,8 @@ export enum EventKinds { REPLACEABLE_LAST = 19999, // Ephemeral events EPHEMERAL_FIRST = 20000, + // NIP-42: Client Authentication + AUTH = 22242, EPHEMERAL_LAST = 29999, // Parameterized replaceable events PARAMETERIZED_REPLACEABLE_FIRST = 30000, @@ -72,6 +74,9 @@ export enum EventTags { Emoji = 'emoji', // NIP-12: geohash tag for location-based queries Geohash = 'g', + // NIP-42: Authentication tags + Challenge = 'challenge', + AuthRelay = 'relay', // Marmot Protocol MIP-03: group ID for filtering kind:445 Group Events Group = 'h', } diff --git a/src/schemas/message-schema.ts b/src/schemas/message-schema.ts index 53b8f09f..c246b4ee 100644 --- a/src/schemas/message-schema.ts +++ b/src/schemas/message-schema.ts @@ -55,4 +55,7 @@ export const countMessageSchema = z export const closeMessageSchema = z.tuple([z.literal(MessageType.CLOSE), subscriptionSchema]) -export const messageSchema = z.union([eventMessageSchema, reqMessageSchema, closeMessageSchema, countMessageSchema]) +// NIP-42 +export const authMessageSchema = z.tuple([z.literal(MessageType.AUTH), eventSchema]) + +export const messageSchema = z.union([eventMessageSchema, reqMessageSchema, closeMessageSchema, countMessageSchema, authMessageSchema]) diff --git a/src/utils/messages.ts b/src/utils/messages.ts index 0b98d5f4..f9189dd0 100644 --- a/src/utils/messages.ts +++ b/src/utils/messages.ts @@ -1,4 +1,5 @@ import { + AuthChallengeMessage, ClosedMessage, CountResultMessage, CountResultPayload, @@ -41,6 +42,11 @@ export const createClosedMessage = (queryId: SubscriptionId, reason: string): Cl return [MessageType.CLOSED, queryId, reason] } +// NIP-42 +export const createAuthChallengeMessage = (challenge: string): AuthChallengeMessage => { + return [MessageType.AUTH, challenge] +} + export const createSubscriptionMessage = ( subscriptionId: SubscriptionId, filters: SubscriptionFilter[], diff --git a/test/unit/schemas/message-schema.spec.ts b/test/unit/schemas/message-schema.spec.ts index 0b5e00a2..562e5b90 100644 --- a/test/unit/schemas/message-schema.spec.ts +++ b/test/unit/schemas/message-schema.spec.ts @@ -167,5 +167,42 @@ describe('NIP-01', () => { expect(result).to.have.property('error').that.is.not.undefined }) }) + + describe('AUTH', () => { + let events: Event[] + beforeEach(() => { + events = getEvents() + }) + + it('returns same message if valid', () => { + const event = events[0] + message = ['AUTH', event] as any + + const result = validateSchema(messageSchema)(message) + expect(result.error).to.be.undefined + expect(result).to.have.deep.property('value', message) + }) + + it('returns error if event is missing', () => { + message = ['AUTH'] as any + + const result = validateSchema(messageSchema)(message) + expect(result).to.have.property('error').that.is.not.undefined + }) + + it('returns error if event is not an object', () => { + message = ['AUTH', 'not-an-event'] as any + + const result = validateSchema(messageSchema)(message) + expect(result).to.have.property('error').that.is.not.undefined + }) + + it('returns error if event is null', () => { + message = ['AUTH', null] as any + + const result = validateSchema(messageSchema)(message) + expect(result).to.have.property('error').that.is.not.undefined + }) + }) }) })