Skip to content

NIP-76: Private Channels - Draft 2 - Includes Client Implementation and Tool kit library#413

Closed
d-krause wants to merge 8 commits intonostr-protocol:masterfrom
d-krause:nip76-draft-2
Closed

NIP-76: Private Channels - Draft 2 - Includes Client Implementation and Tool kit library#413
d-krause wants to merge 8 commits intonostr-protocol:masterfrom
d-krause:nip76-draft-2

Conversation

@d-krause
Copy link
Copy Markdown

@d-krause d-krause commented Apr 4, 2023

Overview

  • Each Private Channel message is encrypted with a unique key, signed with another unique key, and reveals no identifying information about the author or the intended recipient.
  • Event signatures are verified through channel chainCode key derivation.
  • Channel Keys are exchanged via event pointer strings, encrypted (by password or computed secret), which are the keys to a single nostrEvent called an Invitation.

First Client Demo at https://nostr-nip76.web.app/private-channels

For those of you not familiar with blockcore-notes, just login with a private key (demo keys provided in the link), or create an entirely new profile. blockcore-notes prompts you for a password on each signing event. If you did not enter a password during account creation, just leave the password blank, and click OK.
image

Links

@d-krause d-krause changed the title Nip76 draft 2 NIP-76: Private Channels - Draft 2 Apr 4, 2023
@d-krause d-krause changed the title NIP-76: Private Channels - Draft 2 NIP-76: Private Channels - Draft 2 - Includes Client Implementation and Tool kit library Apr 4, 2023
@fyookball
Copy link
Copy Markdown

This looks very nice, whats the status?

@d-krause
Copy link
Copy Markdown
Author

d-krause commented May 2, 2023

This looks very nice, whats the status?

It seems you are the only one that noticed. So thanks for that!

Anyway I am working on Draft 3 of the NIP, (in between 2 real paying gigs). Same concepts are in play, just cleaning up how it works to get there. Hopefully pushing that by mid May, and hopefully getting more feedback on the PR.

@fyookball
Copy link
Copy Markdown

fyookball commented May 4, 2023

Read a bit more it last night. Some initial questions:

  1. The time based indexes makes a lot more sense to me than the sequential index. Wouldn't the sequential index require everyone to keep track of everyone else's entire history and try to find the sequence index?
  2. I understand you are sort of wrapping an event within an event. The outer event (the actual nostr event) has what pubkey? If Alice is in a chat, does she use her normal Alice key? If so, does that contradict what was mentioned about it being private? If not, how do the others in the chat know its Alice, or they dont?
  3. Where is the structure defined for the data to be encrypted, sorry if i missed it. IOW, once you have your password for the invitation and the sequential index, and you decrypt all of it, what is the message that you will then parse?

@d-krause
Copy link
Copy Markdown
Author

d-krause commented May 4, 2023

  1. The time based indexes makes a lot more sense to me than the sequential index. Wouldn't the sequential index require everyone to keep track of everyone else's entire history and try to find the sequence index?

Sequential indexes are a pain to deal with, but I kept them around in only for private indexes (event "threads" that only the creator can read). Sequentials keep third parties from being able find other events on the same index, so we can feel free to pass out information about a channel for example, but the channel information itself is stored on a Sequential index. This way we give keys to friends for one channel, but that does not give them enough information to find other channels we own.

Time Based can be queried by the #e tag. Sequential indexes can only be queried by pubkey because each event has a unique #e tag.

Sequential indexes are a bit laborious, but I am open to ideas that achieve the same results.

  1. I understand you are sort of wrapping an event within an event. The outer event (the actual nostr event) has what pubkey? If Alice is in a chat, does she use her normal Alice key? If so, does that contradict what was mentioned about it being private? If not, how do the others in the chat know its Alice, or they dont?

Yes, Alice's pubkey is encrypted inside the content. We use her private key combined with the chain code of the channel to derive a pubkey and then sign the event. Assuming readers of the event have the encryptKey and signingKey for the channel, they can then decrypt the event, and verify that the owner in the content is the one that derived the key to sign the event.

  1. Where is the structure defined for the data to be encrypted, sorry if i missed it. IOW, once you have your password for the invitation and the sequential index, and you decrypt all of it, what is the message that you will then parse?

Working on this. So far I was trying to eliminate named properties in the content since they can be used attack the encryption. So I have divided the content into 2 staggered arrays:

[Kind, pubkey, tags],
[ Kind Specific properties ]

So Kind Specific properties for Text and React is just one item - the text itself. More complex types would need a more solidified contract. For example, an array of values placed in alphabetic order of the kind's properties? I am open to suggestions.

Examples

event:

{
  "content": "i931mV2qhgpV+07KQX1+lJOLwqS4q0pLD7WA93N5tjQSURyEn4P0pryc4v7YYBE9cDUWYWt+oANCkRo5Q+Msj0kp5hWGgmTBgb3eUV49D6lwjG3yvqeInF6IdS9jKKZ1AVYA3IROADBU3hYIjRPL/QOxALKpeX9Ahpv4P3luEz2gtumhVlRyqH3eMwDeDJNlnGS0Hp+zNcPRqbU4X44sHff4EpTfudzAPMkdTmoGBOrnKFoISiGeTv3BEgHAazaS0IiwHB+MuHza5n9a",
  "created_at": 1680621228,
  "id": "43393c413b35656abf3cd3386d40125685492eee93cff91721c6fe4da71bba7b",
  "kind": 17761,
  "pubkey": "6b9eb473a76f9d4742925a0d8a6fb1424f59beb90a0638cce68b65b142770ca7",
  "sig": "1e3001b657fbb1e29d96e071900fd21ad7ef7775e649190104f57bc981dba5c3e5f2041f6a36c3e70fc55cdb8c39055bb993e4670ff48bdc571a3d3a71b956c0",
  "tags": [
    [
      "e",
      "db1a6d80857ef7d766e82d2725c3d249f26a9f0b6fd129e85d5f1690b3157717"
    ]
  ]
}

Content Decrypted:

[
  [
    41,
    "c94f40831616c246675a134f457e2a18db19570159e920dd62f91b66635982e1",
    null
  ],
  [
    "This Old House",
    "The Fixer Upper",
    "https://planetdp.org/covers/big/0078701.jpg",
    1680621199
  ]
]

Decrypted Content Converted back to NIP defined object

{
  "kind": 41,
  "pubkey": "c94f40831616c246675a134f457e2a18db19570159e920dd62f91b66635982e1",
  "tags": null,
  "name": "This Old House",
  "about": "The Fixer Upper",
  "picture": "https://planetdp.org/covers/big/0078701.jpg",
  "created_at": 1680621199
}

Note This is for a channel metadata kind - I took the liberty of adding an additional created_at inside the content so that I could differentiate when the channel was created vs when the channel event was last edited. Other events would not need this extra value - unless we want to do the same thing with them.

@fyookball
Copy link
Copy Markdown

Ok I think I'm starting to grasp the idea. Let me summarize my understanding and please tell me if it's right:

When Alice wants to post a message to the private channel, (which would normally be a kind 42 in a regular chanel), she constructs a Json string similar to a nostr event (perhaps minus the explicit field names) She signs the "inner Id" with her normal Alice key. She then encrypts the entire JSON string using the group key. Finally, she derives a child key based on her original private key and the created_at field (assuming timestamp based derivation), to create a new pubkey which is used in the outer 17761 message.

Bob , a member of the group reads this message by decrypting it with the group key, then deriving the Child key for the outer message based on the pubkey of the inner message and verifying the signature of both the inner and outer.


Your thought to exclude specific strings is a good one, but then you still have "41" in there. we should have 17761 for 41 and 17762 for 42, etc. right?

@d-krause
Copy link
Copy Markdown
Author

d-krause commented May 5, 2023

When Alice wants to post a message to the private channel, (which would normally be a kind 42 in a regular chanel), she constructs a Json string similar to a nostr event (perhaps minus the explicit field names) She signs the "inner Id" with her normal Alice key. She then encrypts the entire JSON string using the group key. Finally, she derives a child key based on her original private key and the created_at field (assuming timestamp based derivation), to create a new pubkey which is used in the outer 17761 message.

Correct

Bob , a member of the group reads this message by decrypting it with the group key, then deriving the Child key for the outer message based on the pubkey of the inner message and verifying the signature of both the inner and outer.

Correct

Your thought to exclude specific strings is a good one, but then you still have "41" in there. we should have 17761 for 41 and 17762 for 42, etc. right?

Not quite it still is 17761. All NIP76 events are 17761, but inside the encrypted content we declare their "wrapped" Kind, so that we know how to parse the content. This way we can have every kind we want defined for NIP76 Private Channels, not just text notes and replies, but also Reactions, Zaps, Badges, Files, you name it.

Draft 3 of this NIP Proposal will have more of these Kinds demonstrated and the example.

Once inside a NIP76 channel, many Nip Kinds become obsolete / unnecessary. For example in "Clear Text Nostr Land", Kind 42 Channel Message is simply a Kind 1 Text note targeted to a specific Kind 40 channel. NIP76 does not need this kind 42. We can just use Kind 1 (encrypted in the content, 17761 for the event), because simply by posting it with the correct #e tag and signing and encrypting it with the correct key set, we know it is on the channel. NIP 76 would not need Kinds 43 & 44 - because only invited members have the keys needed to post in the channel.

EVERY 17761 Kind that is signed and encrypted it with the correct key set with correct channel #e tag, will be events only visible within that channel, so in the encrypted content we can use any kind we choose to define in the Nip and implement in the code.

More Examples

A text event from Alice on Bob's "This Old House" channel:

{
  "content": "KeBCH4AOBBP9jQ45GBmL4r90oJjxBhpq6IZdJN1Hx0Mt1HHbkjECg1hzG0pBZYp6EToXnAz8o2qQtVdlXNKT2MITaG/SZDlcTS3AXlgFnFwnT+TeUDRKNeOciilk1q4efi6kz8ZcJVNPPmtVpIsxWyEjtX23+but6taDn63I0fZDo4AF1kJhtLOu/Rfr5UAZJ46m8FILwwhC",
  "created_at": 1683288173,
  "id": "2beb01e5a92197fe7422c99a81eb791b64566b625c9c45db08b62746425bc139",
  "kind": 17761,
  "pubkey": "5a5bbd706c787e3103239e4285e0b44b9f5a3654393a151bcc0f30b7351fee40",
  "sig": "9f799ea6243cd40efc976ed7b6bdb5da3305109a1334b9aa1fcbb0f1a03f42cf6b407a64ea574e64418b83c3253657edc2605997a5dcffa72463a4396e4218fc",
  "tags": [
    [
      "e",
      "c72bccceb80484a28c2fd86c0dcf9ba672de168ec8d39bb684efc33b8f8c1db9"
    ]
  ]
}

content decrypted is an array, but we read the first item in the first array to find the "Kind", which is "1", so we can interpret it internally as

{
  "kind": 1,
  "pubkey": "6ea813a435667275c736d722261dc2516c14452c421342a1e6a42046d849c8b3",
  "tags": null,
  "text": "Hi Bob, I need help with my kitchen sink!"
}

Please try the demo - there are Demo Alice and Bob Private keys you can use and paste them into the private key field.

@fyookball
Copy link
Copy Markdown

fyookball commented May 5, 2023

Overall I like the design of this and would be interested in implementing in my client asap; let us know the timeframe when you think the details could be sorted out before I should try to implement.

edit: I understand but I thought you said in a prior response to get rid of predictable substrings , (even "1")

@d-krause
Copy link
Copy Markdown
Author

Sorry it is taking me so long, a full time engagement and family have been very time consuming lately.

I should get my calendar clear in the next week. I just have a few ore things to clean up - I have just had no time to do it yet.

Overall I like the design of this and would be interested in implementing in my client asap; let us know the timeframe when you think the details could be sorted out before I should try to implement.

edit: I understand but I thought you said in a prior response to get rid of predictable substrings , (even "1")

Overall I like the design of this and would be interested in implementing in my client asap; let us know the timeframe when you think the details could be sorted out before I should try to implement.

edit: I understand but I thought you said in a prior response to get rid of predictable substrings , (even "1")

@staab
Copy link
Copy Markdown
Member

staab commented Jun 6, 2023

It seems like this is trying to solve two problems simultaneously:

  • Encrypted channels to wrap regular nostr events
  • A mechanism for spotting missing events

One problem I see that has come up with other things like #468 is that there is no way for a client to filter events except by wrapper pubkey.

I'm having a hard time fully understanding this PR, the cryptography seems pretty complicated but like it doesn't achieve that much, since we're neither using a single key, nor many ephemeral keys? I must be missing something though. I think simpler proposals are going to do better.

@d-krause
Copy link
Copy Markdown
Author

d-krause commented Jun 11, 2023

@staab - Yes - it is trying to solve the two problems simultaneously. When I started this PR there was no Gift Wrap concept being discussed.

The cryptography is actually very simple - I am just not stating it simply enough I guess. Let me try this way:

A Private Channel is created from 2 extended public keys - the signingKey and the encryptKey. An event on the channel chooses the event keys where

  1. authorPubKey = (userPrivateKey + signingKey.ChainCode)/create_date,
  2. encryptionkey = encryptKey/create_date.

Channel events are identified by a derived #e tag that is solely an unintelligent identifier.

This way each event on has a different pubkey and a different encryption key resulting in absolutely no metadata leakage and no way for outsiders to track channel events without possessing the keys.

@staab
Copy link
Copy Markdown
Member

staab commented Jun 12, 2023

There are some nice things about this PR, but #468 and #580 have more traction. I would encourage you to review those PRs and see if you can refine them with your ideas here.

@staab
Copy link
Copy Markdown
Member

staab commented Jul 28, 2023

Closing this as a duplicate, feel free to comment if you think this PR still has value distinct from the others mentioned.

@staab staab closed this Jul 28, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants