nostr-websocket-utils 0.2.4 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (111) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +151 -103
  3. package/dist/__mocks__/extendedWsMock.d.ts +35 -0
  4. package/dist/__mocks__/extendedWsMock.js +156 -0
  5. package/dist/__mocks__/logger.d.ts +9 -0
  6. package/dist/__mocks__/logger.js +6 -0
  7. package/dist/__mocks__/mockLogger.d.ts +41 -0
  8. package/dist/__mocks__/mockLogger.js +47 -0
  9. package/dist/__mocks__/mockserver.d.ts +31 -0
  10. package/dist/__mocks__/mockserver.js +39 -0
  11. package/dist/__mocks__/wsMock.d.ts +26 -0
  12. package/dist/__mocks__/wsMock.js +120 -0
  13. package/dist/client.d.ts +105 -0
  14. package/dist/client.js +105 -0
  15. package/dist/core/client.d.ts +94 -0
  16. package/dist/core/client.js +360 -0
  17. package/dist/core/nostr-server.d.ts +27 -0
  18. package/dist/core/nostr-server.js +95 -0
  19. package/dist/core/queue.d.ts +61 -0
  20. package/dist/core/queue.js +108 -0
  21. package/dist/core/server.d.ts +27 -0
  22. package/dist/core/server.js +114 -0
  23. package/dist/crypto/bech32.d.ts +26 -0
  24. package/dist/crypto/bech32.js +163 -0
  25. package/dist/crypto/handlers.d.ts +11 -0
  26. package/dist/crypto/handlers.js +36 -0
  27. package/dist/crypto/index.d.ts +5 -0
  28. package/dist/crypto/index.js +5 -0
  29. package/dist/crypto/schnorr.d.ts +16 -0
  30. package/dist/crypto/schnorr.js +51 -0
  31. package/dist/endpoints/metrics.d.ts +29 -0
  32. package/dist/endpoints/metrics.js +101 -0
  33. package/dist/index.d.ts +11 -6
  34. package/dist/index.js +16 -4
  35. package/dist/nips/index.d.ts +19 -0
  36. package/dist/nips/index.js +34 -0
  37. package/dist/nips/nip-01.d.ts +34 -0
  38. package/dist/nips/nip-01.js +145 -0
  39. package/dist/nips/nip-02.d.ts +83 -0
  40. package/dist/nips/nip-02.js +123 -0
  41. package/dist/nips/nip-04.d.ts +36 -0
  42. package/dist/nips/nip-04.js +105 -0
  43. package/dist/nips/nip-05.d.ts +86 -0
  44. package/dist/nips/nip-05.js +151 -0
  45. package/dist/nips/nip-09.d.ts +92 -0
  46. package/dist/nips/nip-09.js +190 -0
  47. package/dist/nips/nip-11.d.ts +64 -0
  48. package/dist/nips/nip-11.js +154 -0
  49. package/dist/nips/nip-13.d.ts +73 -0
  50. package/dist/nips/nip-13.js +128 -0
  51. package/dist/nips/nip-15.d.ts +83 -0
  52. package/dist/nips/nip-15.js +101 -0
  53. package/dist/nips/nip-16.d.ts +88 -0
  54. package/dist/nips/nip-16.js +150 -0
  55. package/dist/nips/nip-19.d.ts +28 -0
  56. package/dist/nips/nip-19.js +103 -0
  57. package/dist/nips/nip-20.d.ts +59 -0
  58. package/dist/nips/nip-20.js +95 -0
  59. package/dist/nips/nip-22.d.ts +89 -0
  60. package/dist/nips/nip-22.js +142 -0
  61. package/dist/nips/nip-26.d.ts +52 -0
  62. package/dist/nips/nip-26.js +139 -0
  63. package/dist/nips/nip-28.d.ts +103 -0
  64. package/dist/nips/nip-28.js +170 -0
  65. package/dist/nips/nip-33.d.ts +94 -0
  66. package/dist/nips/nip-33.js +133 -0
  67. package/dist/nostr-server.d.ts +23 -0
  68. package/dist/nostr-server.js +44 -0
  69. package/dist/server.d.ts +13 -3
  70. package/dist/server.js +60 -33
  71. package/dist/transport/base.d.ts +54 -0
  72. package/dist/transport/base.js +104 -0
  73. package/dist/transport/websocket.d.ts +22 -0
  74. package/dist/transport/websocket.js +122 -0
  75. package/dist/types/events.d.ts +63 -0
  76. package/dist/types/events.js +5 -0
  77. package/dist/types/filters.d.ts +19 -0
  78. package/dist/types/filters.js +5 -0
  79. package/dist/types/handlers.d.ts +80 -0
  80. package/dist/types/handlers.js +5 -0
  81. package/dist/types/index.d.ts +118 -39
  82. package/dist/types/index.js +21 -1
  83. package/dist/types/logger.d.ts +40 -0
  84. package/dist/types/logger.js +5 -0
  85. package/dist/types/messages.d.ts +135 -0
  86. package/dist/types/messages.js +40 -0
  87. package/dist/types/nostr.d.ts +120 -39
  88. package/dist/types/nostr.js +5 -10
  89. package/dist/types/options.d.ts +154 -0
  90. package/dist/types/options.js +5 -0
  91. package/dist/types/relays.d.ts +26 -0
  92. package/dist/types/relays.js +5 -0
  93. package/dist/types/scoring.d.ts +47 -0
  94. package/dist/types/scoring.js +29 -0
  95. package/dist/types/socket.d.ts +99 -0
  96. package/dist/types/socket.js +5 -0
  97. package/dist/types/transport.d.ts +97 -0
  98. package/dist/types/transport.js +5 -0
  99. package/dist/types/validation.d.ts +50 -0
  100. package/dist/types/validation.js +5 -0
  101. package/dist/types/websocket.d.ts +172 -0
  102. package/dist/types/websocket.js +5 -0
  103. package/dist/utils/http.d.ts +10 -0
  104. package/dist/utils/http.js +24 -0
  105. package/dist/utils/logger.d.ts +11 -2
  106. package/dist/utils/logger.js +18 -13
  107. package/dist/utils/metrics.d.ts +81 -0
  108. package/dist/utils/metrics.js +206 -0
  109. package/dist/utils/rate-limiter.d.ts +85 -0
  110. package/dist/utils/rate-limiter.js +175 -0
  111. package/package.json +18 -21
@@ -0,0 +1,103 @@
1
+ /**
2
+ * @file NIP-19: bech32-encoded entities
3
+ * @module nips/nip-19
4
+ */
5
+ import { getLogger } from '../utils/logger';
6
+ import { encodeToBech32, decodeFromBech32 } from '../crypto/bech32';
7
+ const logger = getLogger('NIP-19');
8
+ /**
9
+ * Encode a public key to bech32 npub format
10
+ */
11
+ export function encodePubkey(pubkey) {
12
+ try {
13
+ return encodeToBech32('npub', pubkey);
14
+ }
15
+ catch (error) {
16
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error';
17
+ logger.error(`Failed to encode pubkey: ${errorMessage}`);
18
+ throw new Error(`Failed to encode pubkey: ${errorMessage}`);
19
+ }
20
+ }
21
+ /**
22
+ * Encode a private key to bech32 nsec format
23
+ */
24
+ export function encodePrivkey(privkey) {
25
+ try {
26
+ return encodeToBech32('nsec', privkey);
27
+ }
28
+ catch (error) {
29
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error';
30
+ logger.error(`Failed to encode private key: ${errorMessage}`);
31
+ throw new Error(`Failed to encode private key: ${errorMessage}`);
32
+ }
33
+ }
34
+ /**
35
+ * Decode a bech32 npub to hex pubkey
36
+ */
37
+ export function decodePubkey(npub) {
38
+ try {
39
+ const { prefix, hex } = decodeFromBech32(npub);
40
+ if (prefix !== 'npub') {
41
+ throw new Error('Invalid prefix for public key');
42
+ }
43
+ return hex;
44
+ }
45
+ catch (error) {
46
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error';
47
+ logger.error(`Failed to decode pubkey: ${errorMessage}`);
48
+ throw new Error(`Failed to decode pubkey: ${errorMessage}`);
49
+ }
50
+ }
51
+ /**
52
+ * Decode a bech32 nsec to hex privkey
53
+ */
54
+ export function decodePrivkey(nsec) {
55
+ try {
56
+ const { prefix, hex } = decodeFromBech32(nsec);
57
+ if (prefix !== 'nsec') {
58
+ throw new Error('Invalid prefix for private key');
59
+ }
60
+ return hex;
61
+ }
62
+ catch (error) {
63
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error';
64
+ logger.error(`Failed to decode private key: ${errorMessage}`);
65
+ throw new Error(`Failed to decode private key: ${errorMessage}`);
66
+ }
67
+ }
68
+ /**
69
+ * Process tags containing bech32-encoded entities
70
+ */
71
+ export function processBech32Tags(tags) {
72
+ return tags.map(tag => {
73
+ try {
74
+ if (tag[0] === 'p' && tag[1].startsWith('npub')) {
75
+ return [tag[0], decodePubkey(tag[1])];
76
+ }
77
+ return tag;
78
+ }
79
+ catch (error) {
80
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error';
81
+ logger.debug(`Failed to decode pubkey ${tag[1]}: ${errorMessage}`);
82
+ return tag;
83
+ }
84
+ });
85
+ }
86
+ /**
87
+ * Encode tags containing hex pubkeys to bech32
88
+ */
89
+ export function encodeBech32Tags(tags) {
90
+ return tags.map(tag => {
91
+ try {
92
+ if (tag[0] === 'p' && !tag[1].startsWith('npub')) {
93
+ return [tag[0], encodePubkey(tag[1])];
94
+ }
95
+ return tag;
96
+ }
97
+ catch (error) {
98
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error';
99
+ logger.debug(`Failed to encode pubkey ${tag[1]}: ${errorMessage}`);
100
+ return tag;
101
+ }
102
+ });
103
+ }
@@ -0,0 +1,59 @@
1
+ /**
2
+ * @file NIP-20 Command Results implementation
3
+ * @module nips/nip-20
4
+ */
5
+ import { NostrWSMessage } from '../types';
6
+ /**
7
+ * Command status types
8
+ */
9
+ export declare enum CommandStatus {
10
+ SUCCESS = "success",
11
+ ERROR = "error",
12
+ PENDING = "pending",
13
+ RATE_LIMITED = "rate_limited",
14
+ AUTH_REQUIRED = "auth_required",
15
+ RESTRICTED = "restricted"
16
+ }
17
+ export type CommandStatusType = CommandStatus;
18
+ /**
19
+ * Command result interface
20
+ */
21
+ export interface CommandResult {
22
+ status: boolean;
23
+ message?: string;
24
+ code?: CommandStatusType;
25
+ details?: Record<string, unknown>;
26
+ }
27
+ /**
28
+ * Represents the data structure for command messages
29
+ * @interface CommandMessageData
30
+ * @property {string} [event_id] - ID of the event this command relates to
31
+ * @property {boolean} [status] - Status of the command execution
32
+ * @property {string} [message] - Human-readable message about the command result
33
+ * @property {CommandStatusType} [code] - Status code of the command result
34
+ * @property {Record<string, unknown>} [details] - Additional details about the command result
35
+ */
36
+ interface CommandMessageData {
37
+ event_id?: string;
38
+ status?: boolean;
39
+ message?: string;
40
+ code?: CommandStatusType;
41
+ details?: Record<string, unknown>;
42
+ }
43
+ /**
44
+ * Validates a command message according to NIP-20
45
+ */
46
+ export declare function validateCommandMessage(message: NostrWSMessage): boolean;
47
+ /**
48
+ * Creates a command result message
49
+ */
50
+ export declare function createCommandResult(data: CommandMessageData): CommandResult;
51
+ /**
52
+ * Creates an OK message
53
+ */
54
+ export declare function createOkMessage(eventId: string, success?: boolean, details?: Record<string, unknown>): NostrWSMessage;
55
+ /**
56
+ * Creates a NOTICE message
57
+ */
58
+ export declare function createCommandNoticeMessage(code: CommandStatusType, message: string, details?: Record<string, unknown>): NostrWSMessage;
59
+ export {};
@@ -0,0 +1,95 @@
1
+ /**
2
+ * @file NIP-20 Command Results implementation
3
+ * @module nips/nip-20
4
+ */
5
+ import { getLogger } from '../utils/logger';
6
+ const logger = getLogger('NIP-20');
7
+ /**
8
+ * Command status types
9
+ */
10
+ export var CommandStatus;
11
+ (function (CommandStatus) {
12
+ CommandStatus["SUCCESS"] = "success";
13
+ CommandStatus["ERROR"] = "error";
14
+ CommandStatus["PENDING"] = "pending";
15
+ CommandStatus["RATE_LIMITED"] = "rate_limited";
16
+ CommandStatus["AUTH_REQUIRED"] = "auth_required";
17
+ CommandStatus["RESTRICTED"] = "restricted";
18
+ })(CommandStatus || (CommandStatus = {}));
19
+ /**
20
+ * Validates a command message according to NIP-20
21
+ */
22
+ export function validateCommandMessage(message) {
23
+ if (!message.data || typeof message.data !== 'object') {
24
+ logger.debug('Invalid command message data');
25
+ return false;
26
+ }
27
+ const data = message.data;
28
+ // For OK/NOTICE messages
29
+ if (message.type === 'OK') {
30
+ if (!data.event_id || typeof data.event_id !== 'string') {
31
+ logger.debug('Invalid event_id in OK message');
32
+ return false;
33
+ }
34
+ if (typeof data.status !== 'boolean') {
35
+ logger.debug('Invalid status in OK message');
36
+ return false;
37
+ }
38
+ }
39
+ // For NOTICE messages
40
+ if (message.type === 'NOTICE' && data.code) {
41
+ if (!Object.values(CommandStatus).includes(data.code)) {
42
+ logger.debug('Invalid command status code');
43
+ return false;
44
+ }
45
+ }
46
+ // Optional fields validation
47
+ if (data.message && typeof data.message !== 'string') {
48
+ logger.debug('Invalid message field');
49
+ return false;
50
+ }
51
+ if (data.details && typeof data.details !== 'object') {
52
+ logger.debug('Invalid details field');
53
+ return false;
54
+ }
55
+ return true;
56
+ }
57
+ /**
58
+ * Creates a command result message
59
+ */
60
+ export function createCommandResult(data) {
61
+ const status = data.status ?? false;
62
+ const code = status ? CommandStatus.SUCCESS : data.code;
63
+ return {
64
+ status,
65
+ code,
66
+ message: data.message,
67
+ details: data.details
68
+ };
69
+ }
70
+ /**
71
+ * Creates an OK message
72
+ */
73
+ export function createOkMessage(eventId, success = true, details) {
74
+ return {
75
+ type: 'OK',
76
+ data: {
77
+ event_id: eventId,
78
+ status: success,
79
+ ...details && { details }
80
+ }
81
+ };
82
+ }
83
+ /**
84
+ * Creates a NOTICE message
85
+ */
86
+ export function createCommandNoticeMessage(code, message, details) {
87
+ return {
88
+ type: 'NOTICE',
89
+ data: {
90
+ code,
91
+ message,
92
+ ...details && { details }
93
+ }
94
+ };
95
+ }
@@ -0,0 +1,89 @@
1
+ /**
2
+ * @file NIP-22: Event Created At Limits
3
+ * @module nips/nip-22
4
+ * @see https://github.com/nostr-protocol/nips/blob/master/22.md
5
+ */
6
+ import type { NostrWSMessage } from '../types/messages';
7
+ import type { Logger } from '../types/logger';
8
+ /**
9
+ * Default time limits in seconds
10
+ */
11
+ export declare const DEFAULT_TIME_LIMITS: {
12
+ FUTURE_LIMIT: number;
13
+ PAST_LIMIT: number;
14
+ };
15
+ /**
16
+ * Time validation result
17
+ */
18
+ export interface TimeValidationResult {
19
+ valid: boolean;
20
+ reason?: string;
21
+ }
22
+ /**
23
+ * Time validator interface
24
+ */
25
+ export interface TimeValidator {
26
+ /**
27
+ * Validates event timestamp
28
+ * @param timestamp - Event timestamp
29
+ * @returns {TimeValidationResult} Validation result
30
+ */
31
+ validateTime(timestamp: number): TimeValidationResult;
32
+ /**
33
+ * Updates server time offset
34
+ * @param serverTime - Server timestamp
35
+ */
36
+ updateTimeOffset(serverTime: number): void;
37
+ /**
38
+ * Gets current adjusted timestamp
39
+ * @returns {number} Adjusted timestamp
40
+ */
41
+ getCurrentTime(): number;
42
+ }
43
+ /**
44
+ * Creates a time validator
45
+ * @param logger - Logger instance
46
+ * @param futureLimitSeconds - Future time limit in seconds
47
+ * @param pastLimitSeconds - Past time limit in seconds
48
+ * @returns {TimeValidator} Time validator
49
+ */
50
+ export declare function createTimeValidator(logger: Logger, futureLimitSeconds?: number, pastLimitSeconds?: number): TimeValidator;
51
+ /**
52
+ * Validates event timestamp
53
+ * @param message - Message to validate
54
+ * @param validator - Time validator
55
+ * @param logger - Logger instance
56
+ * @returns {TimeValidationResult} Validation result
57
+ */
58
+ export declare function validateEventTime(message: NostrWSMessage, validator: TimeValidator, logger: Logger): TimeValidationResult;
59
+ /**
60
+ * Time synchronization manager interface
61
+ */
62
+ export interface TimeSyncManager {
63
+ /**
64
+ * Starts time synchronization
65
+ * @param wsUrl - WebSocket URL for time sync
66
+ */
67
+ startSync(wsUrl: string): void;
68
+ /**
69
+ * Stops time synchronization
70
+ */
71
+ stopSync(): void;
72
+ /**
73
+ * Gets current synchronized time
74
+ * @returns {number} Current timestamp
75
+ */
76
+ getCurrentTime(): number;
77
+ /**
78
+ * Validates event timing
79
+ * @param event - Event to validate
80
+ * @returns {TimeValidationResult} Validation result
81
+ */
82
+ validateEvent(event: NostrWSMessage): TimeValidationResult;
83
+ }
84
+ /**
85
+ * Creates a time synchronization manager
86
+ * @param logger - Logger instance
87
+ * @returns {TimeSyncManager} Time sync manager
88
+ */
89
+ export declare function createTimeSyncManager(logger: Logger): TimeSyncManager;
@@ -0,0 +1,142 @@
1
+ /**
2
+ * @file NIP-22: Event Created At Limits
3
+ * @module nips/nip-22
4
+ * @see https://github.com/nostr-protocol/nips/blob/master/22.md
5
+ */
6
+ /**
7
+ * Default time limits in seconds
8
+ */
9
+ export const DEFAULT_TIME_LIMITS = {
10
+ FUTURE_LIMIT: 900, // 15 minutes
11
+ PAST_LIMIT: 31536000 // 1 year
12
+ };
13
+ /**
14
+ * Creates a time validator
15
+ * @param logger - Logger instance
16
+ * @param futureLimitSeconds - Future time limit in seconds
17
+ * @param pastLimitSeconds - Past time limit in seconds
18
+ * @returns {TimeValidator} Time validator
19
+ */
20
+ export function createTimeValidator(logger, futureLimitSeconds = DEFAULT_TIME_LIMITS.FUTURE_LIMIT, pastLimitSeconds = DEFAULT_TIME_LIMITS.PAST_LIMIT) {
21
+ let timeOffset = 0; // Offset between local and server time
22
+ return {
23
+ validateTime(timestamp) {
24
+ const now = Date.now() / 1000 + timeOffset;
25
+ // Check future limit
26
+ if (timestamp > now + futureLimitSeconds) {
27
+ return {
28
+ valid: false,
29
+ reason: 'Event timestamp too far in the future'
30
+ };
31
+ }
32
+ // Check past limit
33
+ if (timestamp < now - pastLimitSeconds) {
34
+ return {
35
+ valid: false,
36
+ reason: 'Event timestamp too far in the past'
37
+ };
38
+ }
39
+ return { valid: true };
40
+ },
41
+ updateTimeOffset(serverTime) {
42
+ const localTime = Date.now() / 1000;
43
+ timeOffset = serverTime - localTime;
44
+ logger.debug(`Updated time offset to ${timeOffset} seconds`);
45
+ },
46
+ getCurrentTime() {
47
+ return Math.floor(Date.now() / 1000 + timeOffset);
48
+ }
49
+ };
50
+ }
51
+ /**
52
+ * Validates event timestamp
53
+ * @param message - Message to validate
54
+ * @param validator - Time validator
55
+ * @param logger - Logger instance
56
+ * @returns {TimeValidationResult} Validation result
57
+ */
58
+ export function validateEventTime(message, validator, logger) {
59
+ try {
60
+ if (message.type !== 'EVENT' || !message.data) {
61
+ return { valid: true }; // Not an event message
62
+ }
63
+ const event = message.data;
64
+ const timestamp = event.created_at;
65
+ if (typeof timestamp !== 'number') {
66
+ return {
67
+ valid: false,
68
+ reason: 'Missing or invalid timestamp'
69
+ };
70
+ }
71
+ return validator.validateTime(timestamp);
72
+ }
73
+ catch (error) {
74
+ logger.error('Error validating event time:', error);
75
+ return {
76
+ valid: false,
77
+ reason: 'Error validating timestamp'
78
+ };
79
+ }
80
+ }
81
+ /**
82
+ * Creates a time synchronization manager
83
+ * @param logger - Logger instance
84
+ * @returns {TimeSyncManager} Time sync manager
85
+ */
86
+ export function createTimeSyncManager(logger) {
87
+ const validator = createTimeValidator(logger);
88
+ let syncInterval = null;
89
+ async function syncTime(wsUrl) {
90
+ try {
91
+ const ws = new WebSocket(wsUrl);
92
+ await new Promise((resolve, reject) => {
93
+ ws.onopen = () => {
94
+ // Send time request
95
+ ws.send(JSON.stringify({
96
+ type: 'TIME',
97
+ data: { client_time: Math.floor(Date.now() / 1000) }
98
+ }));
99
+ };
100
+ ws.onmessage = (event) => {
101
+ try {
102
+ const response = JSON.parse(event.data);
103
+ if (response.type === 'TIME') {
104
+ validator.updateTimeOffset(response.data.server_time);
105
+ resolve();
106
+ }
107
+ }
108
+ catch (error) {
109
+ reject(error);
110
+ }
111
+ ws.close();
112
+ };
113
+ ws.onerror = reject;
114
+ // Timeout after 5 seconds
115
+ setTimeout(() => reject(new Error('Time sync timeout')), 5000);
116
+ });
117
+ }
118
+ catch (error) {
119
+ logger.error('Time sync failed:', error);
120
+ }
121
+ }
122
+ return {
123
+ startSync(wsUrl) {
124
+ // Initial sync
125
+ syncTime(wsUrl);
126
+ // Periodic sync every 15 minutes
127
+ syncInterval = setInterval(() => syncTime(wsUrl), 900000);
128
+ },
129
+ stopSync() {
130
+ if (syncInterval) {
131
+ clearInterval(syncInterval);
132
+ syncInterval = null;
133
+ }
134
+ },
135
+ getCurrentTime() {
136
+ return validator.getCurrentTime();
137
+ },
138
+ validateEvent(event) {
139
+ return validateEventTime(event, validator, logger);
140
+ }
141
+ };
142
+ }
@@ -0,0 +1,52 @@
1
+ /**
2
+ * @file NIP-26: Delegated Event Signing
3
+ * @module nips/nip-26
4
+ */
5
+ import type { NostrEvent } from '../types/events';
6
+ /**
7
+ * Represents the conditions for a Nostr event delegation
8
+ * @interface DelegationConditions
9
+ * @property {number} [kind] - The kind of events this delegation is valid for
10
+ * @property {number} [since] - Unix timestamp from which this delegation is valid
11
+ * @property {number} [until] - Unix timestamp until which this delegation is valid
12
+ * @property {unknown} [key: string] - Any additional conditions
13
+ */
14
+ interface DelegationConditions {
15
+ kind?: number;
16
+ since?: number;
17
+ until?: number;
18
+ [key: string]: unknown;
19
+ }
20
+ /**
21
+ * Represents a Nostr event delegation
22
+ * @interface Delegation
23
+ * @property {string} pubkey - The public key of the delegator
24
+ * @property {DelegationConditions} conditions - The conditions under which this delegation is valid
25
+ * @property {string} token - The delegation token signed by the delegator
26
+ */
27
+ interface Delegation {
28
+ pubkey: string;
29
+ conditions: DelegationConditions;
30
+ token: string;
31
+ }
32
+ /**
33
+ * Create a delegation token
34
+ */
35
+ export declare function createDelegation(delegatorPrivkey: string, delegateePubkey: string, conditions: DelegationConditions): Promise<string>;
36
+ /**
37
+ * Verify a delegation token
38
+ */
39
+ export declare function verifyDelegation(delegatorPubkey: string, delegateePubkey: string, token: string, conditions: DelegationConditions): Promise<boolean>;
40
+ /**
41
+ * Add delegation tag to an event
42
+ */
43
+ export declare function addDelegationTag(event: NostrEvent, delegation: Delegation): NostrEvent;
44
+ /**
45
+ * Extract delegation from an event
46
+ */
47
+ export declare function extractDelegation(event: NostrEvent): Delegation | null;
48
+ /**
49
+ * Validate a delegated event
50
+ */
51
+ export declare function validateDelegatedEvent(event: NostrEvent): Promise<boolean>;
52
+ export {};
@@ -0,0 +1,139 @@
1
+ /**
2
+ * @file NIP-26: Delegated Event Signing
3
+ * @module nips/nip-26
4
+ */
5
+ import { getLogger } from '../utils/logger';
6
+ import { signEvent, verifySignature } from 'nostr-crypto-utils';
7
+ const logger = getLogger('NIP-26');
8
+ /**
9
+ * Create a delegation token
10
+ */
11
+ export async function createDelegation(delegatorPrivkey, delegateePubkey, conditions) {
12
+ try {
13
+ const conditionsString = Object.entries(conditions)
14
+ .map(([key, value]) => `${key}=${value}`)
15
+ .sort()
16
+ .join('&');
17
+ const message = `nostr:delegation:${delegateePubkey}:${conditionsString}`;
18
+ // Create a NostrEvent object for signing
19
+ const event = {
20
+ id: '', // This will be set by signEvent
21
+ pubkey: delegateePubkey,
22
+ created_at: Math.floor(Date.now() / 1000),
23
+ kind: 0, // Using kind 0 for delegation events
24
+ tags: [],
25
+ content: message,
26
+ sig: '' // This will be set by signEvent
27
+ };
28
+ const signedEvent = await signEvent(event, delegatorPrivkey);
29
+ return signedEvent.sig;
30
+ }
31
+ catch (error) {
32
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error';
33
+ logger.error(`Failed to create delegation: ${errorMessage}`);
34
+ throw new Error(`Failed to create delegation: ${errorMessage}`);
35
+ }
36
+ }
37
+ /**
38
+ * Verify a delegation token
39
+ */
40
+ export async function verifyDelegation(delegatorPubkey, delegateePubkey, token, conditions) {
41
+ try {
42
+ const conditionsString = Object.entries(conditions)
43
+ .map(([key, value]) => `${key}=${value}`)
44
+ .sort()
45
+ .join('&');
46
+ const message = `nostr:delegation:${delegateePubkey}:${conditionsString}`;
47
+ const verificationEvent = {
48
+ id: '',
49
+ pubkey: delegatorPubkey,
50
+ created_at: Math.floor(Date.now() / 1000),
51
+ kind: 0,
52
+ tags: [],
53
+ content: message,
54
+ sig: token
55
+ };
56
+ return await verifySignature(verificationEvent);
57
+ }
58
+ catch (error) {
59
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error';
60
+ logger.error(`Failed to verify delegation: ${errorMessage}`);
61
+ return false;
62
+ }
63
+ }
64
+ /**
65
+ * Add delegation tag to an event
66
+ */
67
+ export function addDelegationTag(event, delegation) {
68
+ const conditionsString = Object.entries(delegation.conditions)
69
+ .map(([key, value]) => `${key}=${value}`)
70
+ .sort()
71
+ .join('&');
72
+ const delegationTag = ['delegation', delegation.pubkey, conditionsString, delegation.token];
73
+ return {
74
+ ...event,
75
+ tags: [...event.tags, delegationTag]
76
+ };
77
+ }
78
+ /**
79
+ * Extract delegation from an event
80
+ */
81
+ export function extractDelegation(event) {
82
+ try {
83
+ const delegationTag = event.tags.find(tag => tag[0] === 'delegation');
84
+ if (!delegationTag || delegationTag.length !== 4) {
85
+ return null;
86
+ }
87
+ const [, pubkey, conditionsString, token] = delegationTag;
88
+ const conditions = {};
89
+ conditionsString.split('&').forEach(pair => {
90
+ const [key, value] = pair.split('=');
91
+ if (key === 'kind' || key === 'since' || key === 'until') {
92
+ conditions[key] = parseInt(value, 10);
93
+ }
94
+ else {
95
+ conditions[key] = value;
96
+ }
97
+ });
98
+ return { pubkey, conditions, token };
99
+ }
100
+ catch (error) {
101
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error';
102
+ logger.error(`Failed to extract delegation: ${errorMessage}`);
103
+ return null;
104
+ }
105
+ }
106
+ /**
107
+ * Validate a delegated event
108
+ */
109
+ export async function validateDelegatedEvent(event) {
110
+ try {
111
+ const delegation = extractDelegation(event);
112
+ if (!delegation) {
113
+ return false;
114
+ }
115
+ const { kind, created_at } = event;
116
+ const { kind: allowedKind, since, until } = delegation.conditions;
117
+ // Check kind constraint
118
+ if (allowedKind !== undefined && kind !== allowedKind) {
119
+ logger.debug('Event kind does not match delegation conditions');
120
+ return false;
121
+ }
122
+ // Check time constraints
123
+ if (since !== undefined && created_at < since) {
124
+ logger.debug('Event is before delegation start time');
125
+ return false;
126
+ }
127
+ if (until !== undefined && created_at > until) {
128
+ logger.debug('Event is after delegation end time');
129
+ return false;
130
+ }
131
+ // Verify delegation token
132
+ return await verifyDelegation(delegation.pubkey, event.pubkey, delegation.token, delegation.conditions);
133
+ }
134
+ catch (error) {
135
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error';
136
+ logger.error(`Failed to validate delegated event: ${errorMessage}`);
137
+ return false;
138
+ }
139
+ }