Add configurable timestamp randomization for NIP-59 gift wraps

This commit is contained in:
Your Name
2025-10-27 12:57:25 -04:00
parent 9a3965243c
commit a8dc2ed046
6 changed files with 216 additions and 146 deletions

View File

@@ -258,11 +258,12 @@ cJSON* nostr_nip17_create_relay_list_event(const char** relay_urls,
* NIP-17: Send a direct message to recipients
*/
int nostr_nip17_send_dm(cJSON* dm_event,
const char** recipient_pubkeys,
int num_recipients,
const unsigned char* sender_private_key,
cJSON** gift_wraps_out,
int max_gift_wraps) {
const char** recipient_pubkeys,
int num_recipients,
const unsigned char* sender_private_key,
cJSON** gift_wraps_out,
int max_gift_wraps,
long max_delay_sec) {
if (!dm_event || !recipient_pubkeys || num_recipients <= 0 ||
!sender_private_key || !gift_wraps_out || max_gift_wraps <= 0) {
return -1;
@@ -278,13 +279,13 @@ int nostr_nip17_send_dm(cJSON* dm_event,
}
// Create seal for this recipient
cJSON* seal = nostr_nip59_create_seal(dm_event, sender_private_key, recipient_public_key);
cJSON* seal = nostr_nip59_create_seal(dm_event, sender_private_key, recipient_public_key, max_delay_sec);
if (!seal) {
continue; // Skip if sealing fails
}
// Create gift wrap for this recipient
cJSON* gift_wrap = nostr_nip59_create_gift_wrap(seal, recipient_pubkeys[i]);
cJSON* gift_wrap = nostr_nip59_create_gift_wrap(seal, recipient_pubkeys[i], max_delay_sec);
cJSON_Delete(seal); // Seal is now wrapped
if (!gift_wrap) {
@@ -303,10 +304,10 @@ int nostr_nip17_send_dm(cJSON* dm_event,
nostr_bytes_to_hex(sender_public_key, 32, sender_pubkey_hex);
// Create seal for sender
cJSON* sender_seal = nostr_nip59_create_seal(dm_event, sender_private_key, sender_public_key);
cJSON* sender_seal = nostr_nip59_create_seal(dm_event, sender_private_key, sender_public_key, max_delay_sec);
if (sender_seal) {
// Create gift wrap for sender
cJSON* sender_gift_wrap = nostr_nip59_create_gift_wrap(sender_seal, sender_pubkey_hex);
cJSON* sender_gift_wrap = nostr_nip59_create_gift_wrap(sender_seal, sender_pubkey_hex, max_delay_sec);
cJSON_Delete(sender_seal);
if (sender_gift_wrap) {

View File

@@ -97,6 +97,7 @@ cJSON* nostr_nip17_create_relay_list_event(const char** relay_urls,
* @param sender_private_key 32-byte sender private key
* @param gift_wraps_out Array to store resulting gift wrap events (caller must free)
* @param max_gift_wraps Maximum number of gift wraps to create
* @param max_delay_sec Maximum random timestamp delay in seconds (0 = no randomization)
* @return Number of gift wrap events created, or -1 on error
*/
int nostr_nip17_send_dm(cJSON* dm_event,
@@ -104,7 +105,8 @@ int nostr_nip17_send_dm(cJSON* dm_event,
int num_recipients,
const unsigned char* sender_private_key,
cJSON** gift_wraps_out,
int max_gift_wraps);
int max_gift_wraps,
long max_delay_sec);
/**
* NIP-17: Receive and decrypt a direct message

View File

@@ -26,12 +26,18 @@ static void memory_clear(const void *p, size_t len) {
}
/**
* Create a random timestamp within 2 days in the past (as per NIP-59 spec)
* Create a random timestamp within max_delay_sec in the past (configurable)
*/
static time_t random_past_timestamp(void) {
static time_t random_past_timestamp(long max_delay_sec) {
time_t now = time(NULL);
// Random time up to 2 days (172800 seconds) in the past
long random_offset = (long)(rand() % 172800);
// If max_delay_sec is 0, return current timestamp (no randomization)
if (max_delay_sec == 0) {
return now;
}
// Random time up to max_delay_sec in the past
long random_offset = (long)(rand() % max_delay_sec);
return now - random_offset;
}
@@ -104,8 +110,8 @@ cJSON* nostr_nip59_create_rumor(int kind, const char* content, cJSON* tags,
return NULL;
}
// Use provided timestamp or random past timestamp
time_t event_time = (created_at == 0) ? random_past_timestamp() : created_at;
// Use provided timestamp or random past timestamp (default to 0 for compatibility)
time_t event_time = (created_at == 0) ? random_past_timestamp(0) : created_at;
// Create event structure (without id and sig - that's what makes it a rumor)
cJSON* rumor = cJSON_CreateObject();
@@ -142,7 +148,7 @@ cJSON* nostr_nip59_create_rumor(int kind, const char* content, cJSON* tags,
* NIP-59: Create a seal (kind 13) wrapping a rumor
*/
cJSON* nostr_nip59_create_seal(cJSON* rumor, const unsigned char* sender_private_key,
const unsigned char* recipient_public_key) {
const unsigned char* recipient_public_key, long max_delay_sec) {
if (!rumor || !sender_private_key || !recipient_public_key) {
return NULL;
}
@@ -178,7 +184,7 @@ cJSON* nostr_nip59_create_seal(cJSON* rumor, const unsigned char* sender_private
return NULL;
}
time_t seal_time = random_past_timestamp();
time_t seal_time = random_past_timestamp(max_delay_sec);
cJSON_AddStringToObject(seal, "pubkey", sender_pubkey_hex);
cJSON_AddNumberToObject(seal, "created_at", (double)seal_time);
@@ -217,7 +223,7 @@ cJSON* nostr_nip59_create_seal(cJSON* rumor, const unsigned char* sender_private
/**
* NIP-59: Create a gift wrap (kind 1059) wrapping a seal
*/
cJSON* nostr_nip59_create_gift_wrap(cJSON* seal, const char* recipient_public_key_hex) {
cJSON* nostr_nip59_create_gift_wrap(cJSON* seal, const char* recipient_public_key_hex, long max_delay_sec) {
if (!seal || !recipient_public_key_hex) {
return NULL;
}
@@ -272,7 +278,7 @@ cJSON* nostr_nip59_create_gift_wrap(cJSON* seal, const char* recipient_public_ke
return NULL;
}
time_t wrap_time = random_past_timestamp();
time_t wrap_time = random_past_timestamp(max_delay_sec);
cJSON_AddStringToObject(gift_wrap, "pubkey", random_pubkey_hex);
cJSON_AddNumberToObject(gift_wrap, "created_at", (double)wrap_time);

View File

@@ -33,19 +33,21 @@ cJSON* nostr_nip59_create_rumor(int kind, const char* content, cJSON* tags,
* @param rumor The rumor event to seal (cJSON object)
* @param sender_private_key 32-byte sender private key
* @param recipient_public_key 32-byte recipient public key (x-only)
* @param max_delay_sec Maximum random timestamp delay in seconds (0 = no randomization)
* @return cJSON object representing the seal event, or NULL on error
*/
cJSON* nostr_nip59_create_seal(cJSON* rumor, const unsigned char* sender_private_key,
const unsigned char* recipient_public_key);
const unsigned char* recipient_public_key, long max_delay_sec);
/**
* NIP-59: Create a gift wrap (kind 1059) wrapping a seal
*
* @param seal The seal event to wrap (cJSON object)
* @param recipient_public_key_hex Recipient's public key in hex format
* @param max_delay_sec Maximum random timestamp delay in seconds (0 = no randomization)
* @return cJSON object representing the gift wrap event, or NULL on error
*/
cJSON* nostr_nip59_create_gift_wrap(cJSON* seal, const char* recipient_public_key_hex);
cJSON* nostr_nip59_create_gift_wrap(cJSON* seal, const char* recipient_public_key_hex, long max_delay_sec);
/**
* NIP-59: Unwrap a gift wrap to get the seal