node-red-contrib-nostr 0.1.3 → 0.2.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.
@@ -1,8 +1,8 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.EventBuilder = void 0;
4
- const sha256_1 = require("@noble/hashes/sha256");
5
- const utils_1 = require("@noble/hashes/utils");
4
+ const sha2_js_1 = require("@noble/hashes/sha2.js");
5
+ const utils_js_1 = require("@noble/hashes/utils.js");
6
6
  const keys_1 = require("../crypto/keys");
7
7
  class EventBuilder {
8
8
  static async createEvent(kind, content, privateKey, tags = []) {
@@ -26,7 +26,7 @@ class EventBuilder {
26
26
  event.tags,
27
27
  event.content
28
28
  ]);
29
- event.id = (0, utils_1.bytesToHex)((0, sha256_1.sha256)(Buffer.from(serialized)));
29
+ event.id = (0, utils_js_1.bytesToHex)((0, sha2_js_1.sha256)(Buffer.from(serialized)));
30
30
  // Sign the event
31
31
  event.sig = await this.keyManager.sign(privateKey, event.id);
32
32
  return event;
@@ -46,7 +46,7 @@ class EventBuilder {
46
46
  event.tags,
47
47
  event.content
48
48
  ]);
49
- const id = (0, utils_1.bytesToHex)((0, sha256_1.sha256)(Buffer.from(serialized)));
49
+ const id = (0, utils_js_1.bytesToHex)((0, sha2_js_1.sha256)(Buffer.from(serialized)));
50
50
  if (id !== event.id) {
51
51
  return false;
52
52
  }
@@ -1,7 +1,7 @@
1
- export declare const DEFAULT_READER_KEYS: {
1
+ export declare function getDefaultReaderKeys(): Promise<{
2
2
  privateKey: string;
3
3
  publicKey: string;
4
- };
4
+ }>;
5
5
  /**
6
6
  * Generate a new Nostr key pair
7
7
  * @returns {Promise<Object>} Object containing private and public keys
@@ -29,8 +29,6 @@ export declare function npubToHex(npub: string): string;
29
29
  */
30
30
  export declare function getPublicKey(privateKeyHex: string): Promise<string>;
31
31
  export declare class KeyManager {
32
- private secp256k1Promise;
33
- constructor();
34
32
  generatePrivateKey(): Promise<string>;
35
33
  getPublicKey(privateKey: string): Promise<string>;
36
34
  sign(privateKey: string, message: string): Promise<string>;
@@ -1,70 +1,34 @@
1
1
  "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- var desc = Object.getOwnPropertyDescriptor(m, k);
5
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
- desc = { enumerable: true, get: function() { return m[k]; } };
7
- }
8
- Object.defineProperty(o, k2, desc);
9
- }) : (function(o, m, k, k2) {
10
- if (k2 === undefined) k2 = k;
11
- o[k2] = m[k];
12
- }));
13
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
- Object.defineProperty(o, "default", { enumerable: true, value: v });
15
- }) : function(o, v) {
16
- o["default"] = v;
17
- });
18
- var __importStar = (this && this.__importStar) || (function () {
19
- var ownKeys = function(o) {
20
- ownKeys = Object.getOwnPropertyNames || function (o) {
21
- var ar = [];
22
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
- return ar;
24
- };
25
- return ownKeys(o);
26
- };
27
- return function (mod) {
28
- if (mod && mod.__esModule) return mod;
29
- var result = {};
30
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
- __setModuleDefault(result, mod);
32
- return result;
33
- };
34
- })();
35
2
  Object.defineProperty(exports, "__esModule", { value: true });
36
- exports.KeyManager = exports.DEFAULT_READER_KEYS = void 0;
3
+ exports.KeyManager = void 0;
4
+ exports.getDefaultReaderKeys = getDefaultReaderKeys;
37
5
  exports.generateKeyPair = generateKeyPair;
38
6
  exports.hexToNpub = hexToNpub;
39
7
  exports.npubToHex = npubToHex;
40
8
  exports.getPublicKey = getPublicKey;
9
+ const secp256k1_js_1 = require("@noble/curves/secp256k1.js");
41
10
  const bech32_1 = require("bech32");
42
- const utils_1 = require("@noble/hashes/utils");
43
- const sha256_1 = require("@noble/hashes/sha256");
44
- // Default read-only key pair for basic operations
45
- // This is a dedicated key for node-red-contrib-nostr that's only used for reading events
46
- exports.DEFAULT_READER_KEYS = {
47
- privateKey: '5acf32b3374c8c0aa6e483e0f7c6ba8c4b2d2f35d1d5854f08c8c9555d81903b',
48
- publicKey: '04dbc5c6c357e5f33e19c89a2c0b2c1c41f7b15cc3e8f6a63f5e0e8c5d5c5c5c',
49
- };
50
- let secp256k1;
51
- async function initSecp256k1() {
52
- if (!secp256k1) {
53
- secp256k1 = await Promise.resolve().then(() => __importStar(require('@noble/secp256k1')));
11
+ const utils_js_1 = require("@noble/hashes/utils.js");
12
+ const sha2_js_1 = require("@noble/hashes/sha2.js");
13
+ // Lazily-initialized ephemeral key pair for read-only operations.
14
+ // Generated at first use so no secret material is stored in source code.
15
+ let _defaultReaderKeys = null;
16
+ async function getDefaultReaderKeys() {
17
+ if (!_defaultReaderKeys) {
18
+ _defaultReaderKeys = await generateKeyPair();
54
19
  }
55
- return secp256k1;
20
+ return _defaultReaderKeys;
56
21
  }
57
22
  /**
58
23
  * Generate a new Nostr key pair
59
24
  * @returns {Promise<Object>} Object containing private and public keys
60
25
  */
61
26
  async function generateKeyPair() {
62
- const secp = await initSecp256k1();
63
- const privateKey = secp.utils.randomPrivateKey();
64
- const publicKey = secp.getPublicKey(privateKey, true);
27
+ const privateKey = secp256k1_js_1.secp256k1.utils.randomSecretKey();
28
+ const publicKey = secp256k1_js_1.secp256k1.getPublicKey(privateKey, true);
65
29
  return {
66
- privateKey: Buffer.from(privateKey).toString('hex'),
67
- publicKey: Buffer.from(publicKey).toString('hex')
30
+ privateKey: (0, utils_js_1.bytesToHex)(privateKey),
31
+ publicKey: (0, utils_js_1.bytesToHex)(publicKey)
68
32
  };
69
33
  }
70
34
  /**
@@ -91,34 +55,26 @@ function npubToHex(npub) {
91
55
  * @returns {Promise<string>} Public key in hex format
92
56
  */
93
57
  async function getPublicKey(privateKeyHex) {
94
- const secp = await initSecp256k1();
95
- const publicKey = secp.getPublicKey(privateKeyHex, true);
96
- return Buffer.from(publicKey).toString('hex');
58
+ const publicKey = secp256k1_js_1.secp256k1.getPublicKey((0, utils_js_1.hexToBytes)(privateKeyHex), true);
59
+ return (0, utils_js_1.bytesToHex)(publicKey);
97
60
  }
98
61
  class KeyManager {
99
- constructor() {
100
- this.secp256k1Promise = initSecp256k1();
101
- }
102
62
  async generatePrivateKey() {
103
- const secp = await this.secp256k1Promise;
104
- const privateKey = secp.utils.randomPrivateKey();
105
- return Buffer.from(privateKey).toString('hex');
63
+ const privateKey = secp256k1_js_1.secp256k1.utils.randomSecretKey();
64
+ return (0, utils_js_1.bytesToHex)(privateKey);
106
65
  }
107
66
  async getPublicKey(privateKey) {
108
- const secp = await this.secp256k1Promise;
109
- const publicKey = secp.getPublicKey(privateKey, true);
110
- return Buffer.from(publicKey).toString('hex');
67
+ const publicKey = secp256k1_js_1.secp256k1.getPublicKey((0, utils_js_1.hexToBytes)(privateKey), true);
68
+ return (0, utils_js_1.bytesToHex)(publicKey);
111
69
  }
112
70
  async sign(privateKey, message) {
113
- const secp = await this.secp256k1Promise;
114
- const messageHash = (0, sha256_1.sha256)(Buffer.from(message));
115
- const signature = await secp.sign(messageHash, privateKey);
116
- return (0, utils_1.bytesToHex)(signature.toCompactRawBytes());
71
+ const messageHash = (0, sha2_js_1.sha256)(new TextEncoder().encode(message));
72
+ const signature = secp256k1_js_1.secp256k1.sign(messageHash, (0, utils_js_1.hexToBytes)(privateKey));
73
+ return (0, utils_js_1.bytesToHex)(signature);
117
74
  }
118
75
  async verify(publicKey, message, signature) {
119
- const secp = await this.secp256k1Promise;
120
- const messageHash = (0, sha256_1.sha256)(Buffer.from(message));
121
- return secp.verify(signature, messageHash, publicKey);
76
+ const messageHash = (0, sha2_js_1.sha256)(new TextEncoder().encode(message));
77
+ return secp256k1_js_1.secp256k1.verify((0, utils_js_1.hexToBytes)(signature), messageHash, (0, utils_js_1.hexToBytes)(publicKey));
122
78
  }
123
79
  }
124
80
  exports.KeyManager = KeyManager;
@@ -1,55 +1,22 @@
1
1
  "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- var desc = Object.getOwnPropertyDescriptor(m, k);
5
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
- desc = { enumerable: true, get: function() { return m[k]; } };
7
- }
8
- Object.defineProperty(o, k2, desc);
9
- }) : (function(o, m, k, k2) {
10
- if (k2 === undefined) k2 = k;
11
- o[k2] = m[k];
12
- }));
13
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
- Object.defineProperty(o, "default", { enumerable: true, value: v });
15
- }) : function(o, v) {
16
- o["default"] = v;
17
- });
18
- var __importStar = (this && this.__importStar) || (function () {
19
- var ownKeys = function(o) {
20
- ownKeys = Object.getOwnPropertyNames || function (o) {
21
- var ar = [];
22
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
- return ar;
24
- };
25
- return ownKeys(o);
26
- };
27
- return function (mod) {
28
- if (mod && mod.__esModule) return mod;
29
- var result = {};
30
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
- __setModuleDefault(result, mod);
32
- return result;
33
- };
34
- })();
35
2
  Object.defineProperty(exports, "__esModule", { value: true });
36
3
  exports.NIP04 = void 0;
37
- const secp256k1 = __importStar(require("@noble/secp256k1"));
38
- const utils_1 = require("@noble/hashes/utils");
39
- const sha256_1 = require("@noble/hashes/sha256");
4
+ const secp256k1_js_1 = require("@noble/curves/secp256k1.js");
5
+ const utils_js_1 = require("@noble/hashes/utils.js");
6
+ const sha2_js_1 = require("@noble/hashes/sha2.js");
40
7
  const event_1 = require("../core/event");
41
8
  class NIP04 {
42
9
  static async encrypt(privateKey, publicKey, _text) {
43
- const sharedPoint = secp256k1.getSharedSecret((0, utils_1.hexToBytes)(privateKey), (0, utils_1.hexToBytes)(publicKey));
10
+ const sharedPoint = secp256k1_js_1.secp256k1.getSharedSecret((0, utils_js_1.hexToBytes)(privateKey), (0, utils_js_1.hexToBytes)(publicKey));
44
11
  const sharedX = sharedPoint.slice(1, 33);
45
12
  // In a real implementation, we'd use this shared secret with
46
13
  // proper encryption. This is just a placeholder.
47
- const key = (0, sha256_1.sha256)(sharedX);
14
+ const key = (0, sha2_js_1.sha256)(sharedX);
48
15
  // TODO: Implement actual encryption using AES-256-CBC
49
- return (0, utils_1.bytesToHex)(key);
16
+ return (0, utils_js_1.bytesToHex)(key);
50
17
  }
51
18
  static async decrypt(privateKey, publicKey, _encryptedText) {
52
- const sharedPoint = secp256k1.getSharedSecret((0, utils_1.hexToBytes)(privateKey), (0, utils_1.hexToBytes)(publicKey));
19
+ const sharedPoint = secp256k1_js_1.secp256k1.getSharedSecret((0, utils_js_1.hexToBytes)(privateKey), (0, utils_js_1.hexToBytes)(publicKey));
53
20
  const _sharedX = sharedPoint.slice(1, 33);
54
21
  // TODO: Implement actual decryption using AES-256-CBC
55
22
  return "decrypted text";
@@ -34,6 +34,17 @@ var __importStar = (this && this.__importStar) || (function () {
34
34
  })();
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.default = default_1;
37
+ // Allowed fields in a Nostr subscription filter (NIP-01 + NIP-12 tag filters)
38
+ const ALLOWED_FILTER_FIELDS = new Set(['ids', 'authors', 'kinds', 'since', 'until', 'limit', 'search']);
39
+ function sanitizeFilterObject(parsed) {
40
+ const validated = {};
41
+ for (const [key, value] of Object.entries(parsed)) {
42
+ if (ALLOWED_FILTER_FIELDS.has(key) || key.startsWith('#')) {
43
+ validated[key] = value;
44
+ }
45
+ }
46
+ return validated;
47
+ }
37
48
  function default_1(RED) {
38
49
  // Create a function to initialize the node
39
50
  async function initializeNode(config) {
@@ -103,7 +114,8 @@ function default_1(RED) {
103
114
  case 'custom':
104
115
  if (this.customFilter) {
105
116
  try {
106
- const filter = JSON.parse(this.customFilter);
117
+ const parsed = JSON.parse(this.customFilter);
118
+ const filter = sanitizeFilterObject(parsed);
107
119
  shouldForward = Object.entries(filter).every(([key, value]) => {
108
120
  if (Array.isArray(value)) {
109
121
  return value.includes(event[key]);
@@ -147,7 +159,9 @@ function default_1(RED) {
147
159
  case 'custom':
148
160
  if (this.customFilter) {
149
161
  try {
150
- Object.assign(filter, JSON.parse(this.customFilter));
162
+ const parsed = JSON.parse(this.customFilter);
163
+ const validated = sanitizeFilterObject(parsed);
164
+ Object.assign(filter, validated);
151
165
  }
152
166
  catch (err) {
153
167
  this.error("Invalid custom filter: " + err.message);
@@ -1,9 +1,13 @@
1
1
  import { Node, NodeAPI } from 'node-red';
2
2
  import type { NostrWSClient } from 'nostr-websocket-utils';
3
- export interface NostrRelayConfig extends Node {
3
+ interface NostrRelayConfigCredentials {
4
+ privateKey?: string;
5
+ }
6
+ export interface NostrRelayConfig extends Node<NostrRelayConfigCredentials> {
4
7
  relay: string;
5
8
  publicKey?: string;
6
9
  privateKey?: string;
7
10
  _ws?: NostrWSClient;
8
11
  }
9
12
  export default function (RED: NodeAPI): void;
13
+ export {};
@@ -35,13 +35,25 @@ var __importStar = (this && this.__importStar) || (function () {
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.default = default_1;
37
37
  const keys_js_1 = require("../../crypto/keys.js");
38
+ const validation_js_1 = require("../../utils/validation.js");
38
39
  function default_1(RED) {
39
40
  // Create a function to initialize the node
40
41
  async function initializeNode(config) {
41
42
  RED.nodes.createNode(this, config);
42
- this.relay = config.relay;
43
+ // Validate relay URL before accepting
44
+ if (config.relay) {
45
+ if (!validation_js_1.Validation.isValidRelayUrl(config.relay)) {
46
+ this.error('Invalid relay URL: must use ws:// or wss:// protocol and be a valid URL');
47
+ return;
48
+ }
49
+ this.relay = config.relay;
50
+ }
51
+ else {
52
+ this.error('Relay URL is required');
53
+ return;
54
+ }
43
55
  this.publicKey = config.publicKey;
44
- this.privateKey = config.privateKey;
56
+ this.privateKey = this.credentials?.privateKey;
45
57
  // Set up keys based on mode
46
58
  if (this.publicKey && this.privateKey) {
47
59
  try {
@@ -53,9 +65,10 @@ function default_1(RED) {
53
65
  }
54
66
  }
55
67
  else {
56
- // Use default reader keys
57
- this.publicKey = keys_js_1.DEFAULT_READER_KEYS.publicKey;
58
- this.privateKey = keys_js_1.DEFAULT_READER_KEYS.privateKey;
68
+ // Generate ephemeral reader keys
69
+ const readerKeys = await (0, keys_js_1.getDefaultReaderKeys)();
70
+ this.publicKey = readerKeys.publicKey;
71
+ this.privateKey = readerKeys.privateKey;
59
72
  }
60
73
  try {
61
74
  // Dynamically import ESM dependency
@@ -92,10 +105,14 @@ function default_1(RED) {
92
105
  }
93
106
  }
94
107
  // Register the node
95
- RED.nodes.registerType("nostr-relay-config", function (config) {
108
+ RED.nodes.registerType("nostr-relay-config", (function (config) {
96
109
  // Initialize asynchronously
97
110
  initializeNode.call(this, config).catch((err) => {
98
111
  this.error("Failed to initialize node: " + err.message);
99
112
  });
113
+ }), {
114
+ credentials: {
115
+ privateKey: { type: "password" }
116
+ }
100
117
  });
101
118
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "node-red-contrib-nostr",
3
- "version": "0.1.3",
3
+ "version": "0.2.0",
4
4
  "description": "Node-RED nodes for seamless Nostr protocol integration. Features robust WebSocket handling, event filtering, and NPUB-based routing. Built with TypeScript for type safety and extensive testing. Perfect for Nostr automation flows.",
5
5
  "author": "vveerrgg",
6
6
  "type": "commonjs",
@@ -49,11 +49,11 @@
49
49
  }
50
50
  },
51
51
  "dependencies": {
52
- "@noble/hashes": "^1.8.0",
53
- "@noble/secp256k1": "^2.3.0",
52
+ "@noble/curves": "^2.0.1",
53
+ "@noble/hashes": "^2.0.1",
54
54
  "bech32": "^2.0.0",
55
- "nostr-tools": "^1.17.0",
56
- "nostr-websocket-utils": "^0.3.13",
55
+ "nostr-tools": "^2.23.3",
56
+ "nostr-websocket-utils": "^0.4.0",
57
57
  "ws": "^8.19.0"
58
58
  },
59
59
  "devDependencies": {
@@ -63,16 +63,16 @@
63
63
  "@types/ws": "^8.18.1",
64
64
  "@typescript-eslint/eslint-plugin": "^8.56.0",
65
65
  "@typescript-eslint/parser": "^8.56.0",
66
- "@vitest/coverage-v8": "^3.2.4",
67
- "@vitest/ui": "^3.2.4",
66
+ "@vitest/coverage-v8": "^4.0.18",
67
+ "@vitest/ui": "^4.0.18",
68
68
  "eslint": "^10.0.1",
69
69
  "node-red": "^4.1.5",
70
70
  "node-red-node-test-helper": "^0.3.6",
71
71
  "typescript": "^5.9.3",
72
- "vitest": "^3.2.4"
72
+ "vitest": "^4.0.18"
73
73
  },
74
74
  "engines": {
75
- "node": ">=14.0.0"
75
+ "node": ">=18.0.0"
76
76
  },
77
77
  "repository": {
78
78
  "type": "git",