parent
a00417685c
commit
12f1258f3e
client/src
@ -4,6 +4,7 @@
|
||||
|
||||
import { NostrEvent } from '../relay';
|
||||
import * as nostrTools from 'nostr-tools';
|
||||
import { encryptWithNostrTools, encryptWithWebCrypto } from '../utils/crypto-utils';
|
||||
|
||||
/**
|
||||
* Service for managing NIP-21121 HTTP response events
|
||||
@ -40,21 +41,42 @@ export class Nostr21121Service {
|
||||
// Extract private key
|
||||
let privateKeyStr = typeof serverPrivateKey === 'string' ? serverPrivateKey : '';
|
||||
if (typeof serverPrivateKey === 'string' && serverPrivateKey.startsWith('nsec')) {
|
||||
try {
|
||||
// This would normally decode the nsec, but we'll just simulate it
|
||||
console.log('Would decode nsec to hex');
|
||||
privateKeyStr = serverPrivateKey;
|
||||
} catch (e) {
|
||||
console.error('Error decoding nsec:', e);
|
||||
return null;
|
||||
console.log('Using nsec key for signing');
|
||||
} else {
|
||||
console.log('Using hex or bytes key for signing');
|
||||
}
|
||||
|
||||
// Convert the private key to bytes if needed
|
||||
let privateKeyBytes: Uint8Array;
|
||||
|
||||
// Handle different private key formats
|
||||
if (typeof serverPrivateKey === 'string') {
|
||||
if (serverPrivateKey.startsWith('nsec')) {
|
||||
try {
|
||||
// Decode nsec
|
||||
const decoded = nostrTools.nip19.decode(serverPrivateKey);
|
||||
privateKeyBytes = decoded.data as Uint8Array;
|
||||
console.log('Successfully decoded nsec key');
|
||||
} catch (e) {
|
||||
console.error('Error decoding nsec:', e);
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
// Convert hex string to bytes
|
||||
privateKeyBytes = new Uint8Array(
|
||||
serverPrivateKey.match(/.{1,2}/g)?.map(byte => parseInt(byte, 16)) || []
|
||||
);
|
||||
}
|
||||
} else {
|
||||
// Already bytes
|
||||
privateKeyBytes = serverPrivateKey;
|
||||
}
|
||||
|
||||
// Get the public key from the private key
|
||||
const pubKey = nostrTools.getPublicKey(privateKeyStr as any);
|
||||
const pubKey = nostrTools.getPublicKey(privateKeyBytes);
|
||||
console.log(`Using pubkey: ${pubKey.substring(0, 8)}...`);
|
||||
|
||||
// Check if we need to encrypt the response
|
||||
let finalContent = responseContent;
|
||||
// Initialize tags array
|
||||
let tags: string[][] = [];
|
||||
|
||||
// Always add reference to the request event
|
||||
@ -64,42 +86,77 @@ export class Nostr21121Service {
|
||||
|
||||
// Add kind reference
|
||||
tags.push(['k', '21120']);
|
||||
|
||||
// Check if the original event has a p tag (recipient)
|
||||
const pTag = requestEvent.tags.find(tag => tag[0] === 'p');
|
||||
if (pTag && pTag[1]) {
|
||||
// This would be encrypted in a real implementation
|
||||
console.log('Would encrypt content for recipient:', pTag[1]);
|
||||
|
||||
// Add p tag to reference the recipient
|
||||
tags.push(['p', pTag[1], '']);
|
||||
|
||||
// Get the pubkey of the request creator (client) for encryption
|
||||
const clientPubkey = requestEvent.pubkey;
|
||||
if (!clientPubkey) {
|
||||
console.warn("No client pubkey found in request event, encryption will not be possible");
|
||||
}
|
||||
|
||||
// Create the event
|
||||
const eventBody = {
|
||||
// Generate a random encryption key for symmetric encryption
|
||||
const encryptionKey = generateRandomKey(32);
|
||||
console.log("Generated random encryption key for symmetric encryption");
|
||||
|
||||
// Encrypt the content with WebCrypto (symmetric encryption)
|
||||
let encryptedContent: string;
|
||||
try {
|
||||
encryptedContent = await encryptWithWebCrypto(responseContent, encryptionKey);
|
||||
console.log("Successfully encrypted HTTP content with symmetric encryption");
|
||||
} catch (error) {
|
||||
console.error("Symmetric encryption failed:", error);
|
||||
return null;
|
||||
}
|
||||
|
||||
// Encrypt the encryption key with NIP-44 using nostr-tools
|
||||
let encryptedKey: string = "";
|
||||
if (clientPubkey) {
|
||||
try {
|
||||
// Encrypt the encryption key using NIP-44 from nostr-tools
|
||||
console.log(`Encrypting key for client pubkey: ${clientPubkey.substring(0, 8)}...`);
|
||||
// We use privateKeyBytes here since we have it in the correct format
|
||||
encryptedKey = await encryptWithNostrTools(encryptionKey, privateKeyBytes, clientPubkey);
|
||||
console.log("Successfully encrypted the symmetric key with NIP-44");
|
||||
|
||||
// Add p tag to reference the recipient
|
||||
tags.push(['p', clientPubkey, '']);
|
||||
|
||||
// Add encrypted key as a tag
|
||||
tags.push(['encrypted_key', encryptedKey]);
|
||||
} catch (encryptError) {
|
||||
console.error("Failed to encrypt symmetric key:", encryptError);
|
||||
// We'll continue with just the symmetric encryption, but decryption won't work properly
|
||||
console.warn("Proceeding without encrypted key - decryption will not be possible");
|
||||
}
|
||||
}
|
||||
|
||||
// Create the unsigned event
|
||||
const unsignedEvent = {
|
||||
kind: 21121,
|
||||
created_at: Math.floor(Date.now() / 1000),
|
||||
tags: tags,
|
||||
content: finalContent,
|
||||
content: encryptedContent, // This is now the symmetrically encrypted content
|
||||
pubkey: pubKey
|
||||
};
|
||||
|
||||
// Compute the event ID (hash)
|
||||
const id = nostrTools.getEventHash(eventBody);
|
||||
|
||||
// Sign the event (simplified)
|
||||
const sig = 'simulated_signature_for_demo';
|
||||
|
||||
// Create the complete signed event
|
||||
const event: NostrEvent = {
|
||||
...eventBody,
|
||||
id,
|
||||
sig
|
||||
// Sign the event properly using nostr-tools
|
||||
const event = nostrTools.finalizeEvent(unsignedEvent, privateKeyBytes);
|
||||
console.log(`Event created and signed successfully. ID: ${event.id.substring(0, 8)}...`);
|
||||
// The event is already properly signed
|
||||
};
|
||||
|
||||
// Simulate publishing to relay
|
||||
console.log(`Would publish event to relay: ${relayUrl}`);
|
||||
console.log('Event:', event);
|
||||
// Publish to relay
|
||||
console.log(`Publishing event to relay: ${relayUrl}`);
|
||||
|
||||
try {
|
||||
const relayPool = this.relayService.getRelayPool();
|
||||
if (relayPool) {
|
||||
await relayPool.publish([relayUrl], event);
|
||||
console.log('Event published successfully to relay');
|
||||
}
|
||||
} catch (publishError) {
|
||||
console.error('Error publishing event to relay:', publishError);
|
||||
// Even if publishing fails, we'll return the created event
|
||||
}
|
||||
|
||||
return event;
|
||||
} catch (error) {
|
||||
@ -118,4 +175,17 @@ export class Nostr21121Service {
|
||||
console.log(`Would search for response to ${requestEventId} on ${relayUrl}`);
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a random key for symmetric encryption
|
||||
* @param length Length of the key in bytes
|
||||
* @returns A random key as a hex string
|
||||
*/
|
||||
function generateRandomKey(length: number): string {
|
||||
const randomBytes = new Uint8Array(length);
|
||||
crypto.getRandomValues(randomBytes);
|
||||
return Array.from(randomBytes)
|
||||
.map(byte => byte.toString(16).padStart(2, '0'))
|
||||
.join('');
|
||||
}
|
@ -316,4 +316,77 @@ export function decryptKeyWithNostrTools(ciphertext: string, serverNsec: string,
|
||||
reject(new Error(`NIP-44 decryption with nostr-tools failed: ${error instanceof Error ? error.message : String(error)}`));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Encrypt data using NIP-44 with nostr-tools library directly
|
||||
* This is specifically for server-side encryption to the client/requester
|
||||
*
|
||||
* @param plaintext The message to encrypt
|
||||
* @param serverNsec Server's private key in nsec format
|
||||
* @param clientPubkey Client's public key for conversation key derivation (the pubkey of the 21120 request creator)
|
||||
* @returns Promise resolving to NIP-44 encrypted message
|
||||
*/
|
||||
export function encryptWithNostrTools(plaintext: string, serverNsec: string, clientPubkey: string): Promise<string> {
|
||||
return new Promise<string>(async (resolve, reject) => {
|
||||
try {
|
||||
if (!serverNsec.startsWith('nsec')) {
|
||||
reject(new Error("Server key must be in nsec format for encryption"));
|
||||
return;
|
||||
}
|
||||
|
||||
// Import nostr-tools library
|
||||
const nostrTools = await import('nostr-tools');
|
||||
|
||||
// Check if NIP-44 implementation is available in nostr-tools
|
||||
if (!nostrTools.nip44) {
|
||||
reject(new Error("NIP-44 not available in nostr-tools - this may need to be implemented"));
|
||||
return;
|
||||
}
|
||||
|
||||
// Decode the nsec to get the raw private key
|
||||
const decoded = nostrTools.nip19.decode(serverNsec);
|
||||
if (decoded.type !== 'nsec') {
|
||||
reject(new Error(`Not a private key: decoded type is ${decoded.type}`));
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the private key bytes
|
||||
const privateKeyBytes = decoded.data as Uint8Array;
|
||||
|
||||
// Convert client pubkey from npub format if necessary
|
||||
let clientPubkeyHex = clientPubkey;
|
||||
if (clientPubkey.startsWith('npub')) {
|
||||
try {
|
||||
const decodedClient = nostrTools.nip19.decode(clientPubkey);
|
||||
clientPubkeyHex = decodedClient.data as string;
|
||||
} catch (error) {
|
||||
reject(new Error(`Invalid npub format: ${error instanceof Error ? error.message : String(error)}`));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
console.log("Deriving conversation key between server and client for encryption");
|
||||
|
||||
// Get conversation key using the server's private key and client's public key
|
||||
const conversationKey = await nostrTools.nip44.getConversationKey(
|
||||
privateKeyBytes, // Server private key as bytes
|
||||
clientPubkeyHex // Client public key as hex string (21120 request creator)
|
||||
);
|
||||
|
||||
console.log("Generated conversation key for encryption");
|
||||
|
||||
// Encrypt the message using NIP-44 with the derived conversation key
|
||||
const encryptedMessage = await nostrTools.nip44.encrypt(
|
||||
plaintext, // The plaintext message (HTTP response)
|
||||
conversationKey // The conversation key we derived
|
||||
);
|
||||
|
||||
console.log("Successfully encrypted message with NIP-44 conversation key");
|
||||
resolve(encryptedMessage);
|
||||
} catch (error) {
|
||||
console.error("Error in encryptWithNostrTools:", error);
|
||||
reject(new Error(`NIP-44 encryption with nostr-tools failed: ${error instanceof Error ? error.message : String(error)}`));
|
||||
}
|
||||
});
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user