Oddbean new post about | logout
 https://github.com/vitorpamplona/lazereyes/blob/main/js/nip17.js 
 Just call createNIp17wraps with the message and keys.  
 async function createNip17Wraps(message, senderPubkey, receiverPubkey)

Where do I stick my SK into? And what do I do with the returned values that are in non-event format? 
 Humm.. you might need to tweak the code. This uses the browser's expression to sign via window.nostr... 

The returned values are two wraps for the sender and the receiver. All you need to do is to broadcast them to the relays each one uses.  
 Ok, I’ll take a look again and see if I can implement it. I assume that I do not need to send the “sender” part since it is OTP and nobody needs to check it as a sender 
 Yep, I guess it would be even better since the sender doesn't need a copy of the code. 
 I implemented it for the nostr.build just now but not sure if it even works. I still send 04 and now 17 as well. I have no way to verify it 😅 
 I will check tomorrow, but I only got the nip04. 

Also, you should ask for the nprofile in the login page. In that way, you will have the relay info to get the DM relays and send the DMs. :)  
 Is the code open somewhere?  
 😅 the code that does the sending is a complete mess and has a non-public info in it. I’ll try to clean it up and open the source for it. Need to parameterize some of the non-public (but not a high value secret or anything) info. 
 t-y Fishcake 
 This took me way longer than I would have liked, but here is the working Typescript code that makes those god damn GW DMs.

// Create DM event
export async function creatNIP17DMEvent(
  fromSk: string,
  toNpub: string,
  dm: string[],
): Promise<Array<Event>> {
  const skBuff = hexToBytes(fromSk)
  const events: Array<Event> = []

  while (dm.length > 0) {
    const msg = dm.shift()
    if (msg)
      events.push(createNip17Wraps(msg, toNpub, skBuff))
  }

  return events
}

function getNow(): number {
  return Math.floor(Date.now() / 1000)
}

function getRandomNow(): number {
  return getNow() - Math.floor(Math.random() * 172800)
}

function nip44Encrypt(rumor: Rumor, privateKey: Uint8Array, publicKey: string): string {
  const key = nip44.v2.utils.getConversationKey(privateKey, publicKey)
  return nip44.v2.encrypt(JSON.stringify(rumor), key)
}

function createWrap(event: Event, recipientPublicKey: string): Event {
  // Get random secret key to wrap the event
  const randomSK = generateSecretKey()
  return finalizeEvent({
    kind: 1059, // Gift wrapped event
    content: nip44Encrypt(event, randomSK, recipientPublicKey),
    created_at: getRandomNow(),
    tags: [["p", recipientPublicKey]]
  }, randomSK) as Event
}

function createSeal(event: Rumor, privateKey: Uint8Array, recipientPublicKey: string): Event {
  return finalizeEvent({
    kind: 13, // Seal
    content: nip44Encrypt(event, privateKey, recipientPublicKey),
    created_at: getRandomNow(),
    tags: []
  }, privateKey)
}

function createRumor(message: string, senderPubkey: string, receiverPubkey: string): Rumor {
  const rumor = {
    kind: 14, // As per specs, should be Kind 14 event
    content: message,
    pubkey: senderPubkey,
    created_at: getNow(),
    tags: [["p", receiverPubkey]],
  } as Rumor
  rumor.id = getEventHash(rumor)
  return rumor
}

function createNip17Wraps(message: string, recipientPublicKey: string, senderSk: Uint8Array): Event {
  const senderPubkey = getPublicKey(senderSk)
  return createWrap(
    createSeal(
      createRumor(message, senderPubkey, recipientPublicKey),
      senderSk, recipientPublicKey),
    recipientPublicKey)
} 
 One fix for types:

function nip44Encrypt(rumor: Rumor | VerifiedEvent, privateKey: Uint8Array, publicKey: string): string {
  const key = nip44.v2.utils.getConversationKey(privateKey, publicKey)
  return nip44.v2.encrypt(JSON.stringify(rumor), key)
}


It does not have any impact, just cosmetics 
 It works! :)  
 Yeah! I do suggest to make a clear documentation about how it should be done, I will contribute if needed. It is super confusing now and no single NIP to look at 🫂😭 
 Maybe send a PR with some of this code to nostr-tools?  
 Good idea! 🫂 
 I should add nip40 expiration to the wrap too I think, if the event is OTP 
 Great idea  
 I had some other issues, and didn’t have the messages actually sent anywhere, now they are but none of the clients see them only 04. Hence my point, if it is complex and close to impossible to implement even with examples, it will not be used or will be misused or misimplemented 😭😭😭 
 Very good boy
You can tag me in one and send to relay.utxo.one/chat I'll help you test 🐶🐾

Relay will give you an error right away if it didn't work
 https://i.nostr.build/lyNDHtFTkFQU6r0L.jpg 
 I am blasting the DMs, and they go out but none of them are seen by the client. My conclusion is that nip17 sucks ass, and only focuses on solving c2c but not s2c problem. I cannot have DMs sent to a specific relay, I don’t know who is using what and when, I need for DMs to reach the recipient and for me to do not need to know about their nprofile or anything else besides their npub, I cannot rely on any relay to have any info about any npub, I need to send it fast and with no WS fuss that is client oriented shit. So many complaints 😭😭😭😅 
 Thanks! This will help ! 🫂🙏🏻🫡