Compare commits
1 Commits
relay-hint
...
nip-17-dms
| Author | SHA1 | Date | |
|---|---|---|---|
|
922c705dd0
|
@@ -26,7 +26,7 @@ struct NotificationFormatter {
|
||||
content.title = NSLocalizedString("Someone posted a note", comment: "Title label for push notification where someone posted a note")
|
||||
content.body = event.content
|
||||
break
|
||||
case .dm:
|
||||
case .deprecated_dm:
|
||||
content.title = NSLocalizedString("New message", comment: "Title label for push notifications where a direct message was sent to the user")
|
||||
content.body = NSLocalizedString("(Contents are encrypted)", comment: "Label on push notification indicating that the contents of the message are encrypted")
|
||||
break
|
||||
|
||||
@@ -12,6 +12,12 @@
|
||||
3169CAE6294E69C000EE4006 /* EmptyTimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3169CAE5294E69C000EE4006 /* EmptyTimelineView.swift */; };
|
||||
3169CAED294FCCFC00EE4006 /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3169CAEC294FCCFC00EE4006 /* Constants.swift */; };
|
||||
31D2E847295218AF006D67F8 /* Shimmer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31D2E846295218AF006D67F8 /* Shimmer.swift */; };
|
||||
3A04DA252E1F40AC00449A0B /* NIP17DirectMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A04DA212E1F40AC00449A0B /* NIP17DirectMessage.swift */; };
|
||||
3A04DA262E1F40AC00449A0B /* NIP59GiftWrap.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A04DA232E1F40AC00449A0B /* NIP59GiftWrap.swift */; };
|
||||
3A04DA272E1F40AC00449A0B /* NIP17DirectMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A04DA212E1F40AC00449A0B /* NIP17DirectMessage.swift */; };
|
||||
3A04DA282E1F40AC00449A0B /* NIP59GiftWrap.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A04DA232E1F40AC00449A0B /* NIP59GiftWrap.swift */; };
|
||||
3A04DA292E1F40AC00449A0B /* NIP17DirectMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A04DA212E1F40AC00449A0B /* NIP17DirectMessage.swift */; };
|
||||
3A04DA2A2E1F40AC00449A0B /* NIP59GiftWrap.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A04DA232E1F40AC00449A0B /* NIP59GiftWrap.swift */; };
|
||||
3A0A30BB2C21397A00F8C9BC /* EmojiPicker in Frameworks */ = {isa = PBXBuildFile; productRef = 3A0A30BA2C21397A00F8C9BC /* EmojiPicker */; };
|
||||
3A23838E2A297DD200E5AA2E /* ZapButtonModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A23838D2A297DD200E5AA2E /* ZapButtonModel.swift */; };
|
||||
3A2BAC5A2DD7E4C400EBB4CC /* NIP05DomainTimelineHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A2BAC592DD7E4C400EBB4CC /* NIP05DomainTimelineHeaderView.swift */; };
|
||||
@@ -1862,6 +1868,8 @@
|
||||
3169CAE5294E69C000EE4006 /* EmptyTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmptyTimelineView.swift; sourceTree = "<group>"; };
|
||||
3169CAEC294FCCFC00EE4006 /* Constants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = Constants.swift; path = damus/Util/Constants.swift; sourceTree = SOURCE_ROOT; };
|
||||
31D2E846295218AF006D67F8 /* Shimmer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Shimmer.swift; sourceTree = "<group>"; };
|
||||
3A04DA212E1F40AC00449A0B /* NIP17DirectMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NIP17DirectMessage.swift; sourceTree = "<group>"; };
|
||||
3A04DA232E1F40AC00449A0B /* NIP59GiftWrap.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NIP59GiftWrap.swift; sourceTree = "<group>"; };
|
||||
3A185A04297F2C3800F4BDC0 /* lv-LV */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "lv-LV"; path = "lv-LV.lproj/InfoPlist.strings"; sourceTree = "<group>"; };
|
||||
3A185A05297F2C3800F4BDC0 /* lv-LV */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "lv-LV"; path = "lv-LV.lproj/Localizable.strings"; sourceTree = "<group>"; };
|
||||
3A185A06297F2C3800F4BDC0 /* lv-LV */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = "lv-LV"; path = "lv-LV.lproj/Localizable.stringsdict"; sourceTree = "<group>"; };
|
||||
@@ -2784,6 +2792,22 @@
|
||||
path = "Empty Views";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
3A04DA222E1F40AC00449A0B /* NIP17 */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
3A04DA212E1F40AC00449A0B /* NIP17DirectMessage.swift */,
|
||||
);
|
||||
path = NIP17;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
3A04DA242E1F40AC00449A0B /* NIP59 */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
3A04DA232E1F40AC00449A0B /* NIP59GiftWrap.swift */,
|
||||
);
|
||||
path = NIP59;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
3A515C4E2DF4E0E6002D3B34 /* Tips */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@@ -3794,6 +3818,8 @@
|
||||
4CE6DEE527F7A08100C66700 /* damus */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
3A04DA222E1F40AC00449A0B /* NIP17 */,
|
||||
3A04DA242E1F40AC00449A0B /* NIP59 */,
|
||||
D76BE18A2E0CF3BF004AD0C6 /* DIP06 */,
|
||||
D71527FD2E0A3D5800C893D6 /* NIP51 */,
|
||||
D7DB93082D69478400DA1EE5 /* NIP65 */,
|
||||
@@ -5072,6 +5098,8 @@
|
||||
4CA352A82A76B37E003BB08B /* NewMutesNotify.swift in Sources */,
|
||||
4CFF8F6929CC9ED1008DB934 /* ImageContainerView.swift in Sources */,
|
||||
7527271E2A93FF0100214108 /* Block.swift in Sources */,
|
||||
3A04DA252E1F40AC00449A0B /* NIP17DirectMessage.swift in Sources */,
|
||||
3A04DA262E1F40AC00449A0B /* NIP59GiftWrap.swift in Sources */,
|
||||
4C54AA0729A540BA003E4487 /* NotificationsModel.swift in Sources */,
|
||||
4CE4F0F229D4FCFA005914DB /* DebouncedOnChange.swift in Sources */,
|
||||
4C32B9592A9AD44700DC3548 /* Table.swift in Sources */,
|
||||
@@ -5234,6 +5262,8 @@
|
||||
82D6FAAC2CD99F7900C925F4 /* FlatBufferBuilder.swift in Sources */,
|
||||
82D6FAAD2CD99F7900C925F4 /* FlatbuffersErrors.swift in Sources */,
|
||||
82D6FAAE2CD99F7900C925F4 /* Verifier.swift in Sources */,
|
||||
3A04DA292E1F40AC00449A0B /* NIP17DirectMessage.swift in Sources */,
|
||||
3A04DA2A2E1F40AC00449A0B /* NIP59GiftWrap.swift in Sources */,
|
||||
82D6FAAF2CD99F7900C925F4 /* ByteBuffer.swift in Sources */,
|
||||
82D6FAB02CD99F7900C925F4 /* TableVerifier.swift in Sources */,
|
||||
82D6FAB12CD99F7900C925F4 /* Root.swift in Sources */,
|
||||
@@ -6216,6 +6246,8 @@
|
||||
D73E5E172C6A962A007EB227 /* ImageUploadModel.swift in Sources */,
|
||||
D703D76A2C670B2C00A400EA /* Bech32Object.swift in Sources */,
|
||||
D73E5E162C6A9619007EB227 /* PostView.swift in Sources */,
|
||||
3A04DA272E1F40AC00449A0B /* NIP17DirectMessage.swift in Sources */,
|
||||
3A04DA282E1F40AC00449A0B /* NIP59GiftWrap.swift in Sources */,
|
||||
D703D7872C670C7E00A400EA /* DamusPurpleEnvironment.swift in Sources */,
|
||||
D703D7892C670C8600A400EA /* DeepLPlan.swift in Sources */,
|
||||
D73E5E182C6A963D007EB227 /* AttachMediaUtility.swift in Sources */,
|
||||
|
||||
@@ -12,7 +12,7 @@ enum NoteContent {
|
||||
case content(String, TagsSequence?)
|
||||
|
||||
init(note: NostrEvent, keypair: Keypair) {
|
||||
if note.known_kind == .dm || note.known_kind == .highlight {
|
||||
if note.known_kind == .deprecated_dm || note.known_kind == .highlight {
|
||||
self = .content(note.get_content(keypair), note.tags)
|
||||
} else {
|
||||
self = .note(note)
|
||||
|
||||
@@ -205,7 +205,7 @@ class HomeModel: ContactsDelegate {
|
||||
handle_boost_event(sub_id: sub_id, ev)
|
||||
case .like:
|
||||
handle_like_event(ev)
|
||||
case .dm:
|
||||
case .deprecated_dm:
|
||||
handle_dm(ev)
|
||||
case .delete:
|
||||
handle_delete_event(ev)
|
||||
@@ -231,6 +231,13 @@ class HomeModel: ContactsDelegate {
|
||||
break
|
||||
case .interest_list:
|
||||
break // Don't care for now
|
||||
case .dm:
|
||||
break // We should never receive a kind 14 DM. It will always be sealed (kind 13) and then gift wrapped (kind 1059).
|
||||
case .seal:
|
||||
break // We should never receive a kind 13 seal. It will always be gift wrapped (kind 1059)
|
||||
case .gift_wrap:
|
||||
handle_gift_wrap(ev)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
@@ -563,9 +570,9 @@ class HomeModel: ContactsDelegate {
|
||||
var our_blocklist_filter = NostrFilter(kinds: [.mute_list])
|
||||
our_blocklist_filter.authors = [damus_state.pubkey]
|
||||
|
||||
var dms_filter = NostrFilter(kinds: [.dm])
|
||||
var dms_filter = NostrFilter(kinds: [.deprecated_dm, .gift_wrap])
|
||||
|
||||
var our_dms_filter = NostrFilter(kinds: [.dm])
|
||||
var our_dms_filter = NostrFilter(kinds: [.deprecated_dm])
|
||||
|
||||
// friends only?...
|
||||
//dms_filter.authors = friends
|
||||
@@ -814,6 +821,19 @@ class HomeModel: ContactsDelegate {
|
||||
self.incoming_dms = []
|
||||
}
|
||||
}
|
||||
|
||||
func handle_gift_wrap(_ ev: NostrEvent) {
|
||||
guard ev.known_kind == .gift_wrap,
|
||||
let privateKey = damus_state.keypair.privkey else {
|
||||
return
|
||||
}
|
||||
|
||||
guard let rumor = try? NIP59GiftWrap.unsealedRumor(giftWrapEvent: ev, using: privateKey) else {
|
||||
return
|
||||
}
|
||||
|
||||
handle_dm(rumor)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -113,7 +113,7 @@ func generate_local_notification_object(from ev: NostrEvent, state: HeadlessDamu
|
||||
return LocalNotification(type: .like, event: ev, target: .note_id(evid), content: "")
|
||||
}
|
||||
}
|
||||
else if type == .dm,
|
||||
else if type == .deprecated_dm,
|
||||
state.settings.dm_notification {
|
||||
let convo = ev.decrypted(keypair: state.keypair) ?? NSLocalizedString("New encrypted direct message", comment: "Notification that the user has received a new direct message")
|
||||
return LocalNotification(type: .dm, event: ev, target: .note(ev), content: convo)
|
||||
|
||||
27
damus/NIP17/NIP17DirectMessage.swift
Normal file
27
damus/NIP17/NIP17DirectMessage.swift
Normal file
@@ -0,0 +1,27 @@
|
||||
//
|
||||
// NIP17.swift
|
||||
// damus
|
||||
//
|
||||
// Created by Terry Yiu on 6/6/25.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
/// Functions and utilities for the NIP-04 spec
|
||||
struct NIP17 {}
|
||||
|
||||
extension NIP17 {
|
||||
/// Creates a sealed and gift wrapped kind 14 direct message event. The kind 14 direct message will not be signed because the message might leak to relays and become fully public.
|
||||
static func giftWrappedDirectMessage(message: String, senderKeypair: FullKeypair, receiverPubkey: Pubkey) -> NostrEvent? {
|
||||
let tags = [
|
||||
["p", receiverPubkey.hex()] // TODO add receiver relay URL
|
||||
]
|
||||
|
||||
guard let unsignedDM = NostrEvent(content: message, keypair: .just_pubkey(senderKeypair.pubkey), kind: NostrKind.dm.rawValue, tags: tags)
|
||||
else {
|
||||
return nil;
|
||||
}
|
||||
|
||||
return try? NIP59GiftWrap.giftWrap(withRumor: unsignedDM, toRecipient: receiverPubkey, signedBy: senderKeypair)
|
||||
}
|
||||
}
|
||||
190
damus/NIP59/NIP59GiftWrap.swift
Normal file
190
damus/NIP59/NIP59GiftWrap.swift
Normal file
@@ -0,0 +1,190 @@
|
||||
//
|
||||
// NIP59GiftWrap.swift
|
||||
// damus
|
||||
//
|
||||
// Created by Terry Yiu on 6/6/25.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
struct NIP59GiftWrap {
|
||||
/// Creates a ``NostrEvent`` gift wrap of kind 1059 that takes a rumor, an unsigned ``NostrEvent``, and seals it in a signed ``NostrEvent`` seal event of kind 13, and then wraps that seal encrypted in the content of the gift wrap.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - rumor: a ``NostrEvent`` that is not signed.
|
||||
/// - recipient: the ``Pubkey`` of the receiver of the event. This pubkey will be used to encrypt the rumor. If `recipientAlias` is not provided, this pubkey will automatically be added as a tag to the ``NostrEvent`` gift wrap event.
|
||||
/// - recipientAlias: optional ``Pubkey`` of the receiver's alias used to receive gift wraps without exposing the receiver's identity. It is not used to encrypt the rumor. If it is provided, this pubkey will automatically be added as a tag to the ``NostrEvent`` gift wrap event.
|
||||
/// - tags: the list of tags to add to the ``NostrEvent`` gift wrap event in addition to the pubkey tag from `toRecipient`. This list should include any information needed to route the event to its intended recipient, such as [NIP-13 Proof of Work](https://github.com/nostr-protocol/nips/blob/master/13.md).
|
||||
/// - createdAt: the creation timestamp of the seal. Note that this timestamp SHOULD be tweaked to thwart time-analysis attacks. Note that some relays don't serve events dated in the future, so all timestamps SHOULD be in the past. By default, if `createdAt` is not provided, a random timestamp within 2 days in the past will be chosen.
|
||||
/// - keypair: The real ``FullKeypair`` to encrypt the rumor and sign the seal with. Note that a different random one-time use key is used to sign the gift wrap.
|
||||
static func giftWrap(
|
||||
withRumor rumor: NostrEvent,
|
||||
toRecipient recipient: Pubkey,
|
||||
recipientAlias: Pubkey? = nil,
|
||||
tags: [Tag] = [],
|
||||
createdAt: UInt32 = UInt32(max(0, Date.now.timeIntervalSince1970 - TimeInterval.random(in: 0...172800))),
|
||||
signedBy fullKeypair: FullKeypair
|
||||
) throws -> NostrEvent? {
|
||||
guard let seal = try seal(withRumor: rumor, toRecipient: recipient, signedBy: fullKeypair) else {
|
||||
throw SealEventError.sealFailed
|
||||
}
|
||||
return try giftWrap(withSeal: seal, toRecipient: recipient, recipientAlias: recipientAlias, tags: tags, createdAt: createdAt)
|
||||
}
|
||||
|
||||
/// Creates a ``NostrEvent`` gift wrap of kind 1059 that takes a signed ``NostrEvent`` of kind 13, and then wraps that seal encrypted in the content of the gift wrap.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - seal: a signed ``NostrEvent`` seal event of kind 13.
|
||||
/// - recipient: the ``Pubkey`` of the receiver of the event. This pubkey will be used to encrypt the rumor. If `recipientAlias` is not provided, this pubkey will automatically be added as a tag to the ``GiftWrapEvent``.
|
||||
/// - recipientAlias: optional ``Pubkey`` of the receiver's alias used to receive gift wraps without exposing the receiver's identity. It is not used to encrypt the rumor. If it is provided, this pubkey will automatically be added as a tag to the ``GiftWrapEvent``.
|
||||
/// - tags: the list of tags.
|
||||
/// - createdAt: the creation timestamp of the seal. Note that this timestamp SHOULD be tweaked to thwart time-analysis attacks. Note that some relays don't serve events dated in the future, so all timestamps SHOULD be in the past. By default, if `createdAt` is not provided, a random timestamp within 2 days in the past will be chosen.
|
||||
static func giftWrap(
|
||||
withSeal seal: NostrEvent,
|
||||
toRecipient recipient: Pubkey,
|
||||
recipientAlias: Pubkey? = nil,
|
||||
tags: [Tag] = [],
|
||||
createdAt: UInt32 = UInt32(max(0, Date.now.timeIntervalSince1970 - TimeInterval.random(in: 0...172800))),
|
||||
) throws -> NostrEvent? {
|
||||
guard seal.known_kind == .seal else {
|
||||
throw GiftWrapError.sealInvalid
|
||||
}
|
||||
|
||||
let jsonData = try JSONEncoder().encode(seal)
|
||||
guard let stringifiedJSON = String(data: jsonData, encoding: .utf8) else {
|
||||
throw GiftWrapError.utf8EncodingFailed
|
||||
}
|
||||
|
||||
let randomFullKeypair = generate_new_keypair()
|
||||
|
||||
let combinedTags = [["p", (recipientAlias ?? recipient).hex()]] + tags.map { $0.strings() }
|
||||
|
||||
let encryptedSeal = try NIP44v2Encryption.encrypt(plaintext: stringifiedJSON, privateKeyA: randomFullKeypair.privkey, publicKeyB: recipient)
|
||||
return NostrEvent(content: encryptedSeal, keypair: randomFullKeypair.to_keypair(), kind: NostrKind.gift_wrap.rawValue, tags: combinedTags, createdAt: createdAt)
|
||||
}
|
||||
|
||||
/// Unwraps the content of the gift wrap event and decrypts it into a ``NostrEvent`` seal event.
|
||||
/// - Parameters:
|
||||
/// - giftWrapEvent: The ``NostrEvent`` gift wrap kind 1059 to unwrap.
|
||||
/// - privateKey: The ``Privkey`` to decrypt the content.
|
||||
/// - Returns: The ``SealEvent``.
|
||||
static func unwrappedSeal(giftWrapEvent: NostrEvent, using privateKey: Privkey) throws -> NostrEvent? {
|
||||
guard giftWrapEvent.known_kind == .gift_wrap else {
|
||||
throw GiftWrapError.giftWrapInvalid
|
||||
}
|
||||
|
||||
guard let unwrappedSeal = try? NIP44v2Encryption.decrypt(payload: giftWrapEvent.content, privateKeyA: privateKey, publicKeyB: giftWrapEvent.pubkey) else {
|
||||
throw GiftWrapError.decryptionFailed
|
||||
}
|
||||
|
||||
guard let sealJSONData = unwrappedSeal.data(using: .utf8) else {
|
||||
throw GiftWrapError.utf8EncodingFailed
|
||||
}
|
||||
|
||||
guard let sealEvent = try? JSONDecoder().decode(NostrEvent.self, from: sealJSONData) else {
|
||||
throw GiftWrapError.jsonDecodingFailed
|
||||
}
|
||||
|
||||
return sealEvent
|
||||
}
|
||||
|
||||
/// Creates a ``NostrEvent`` seal event of kind 13 that encrypts a rumor with the sender's private key and receiver's public key.
|
||||
/// There is no p tag pointing to the receiver. There is no way to know who the rumor is for without the receiver's or the sender's private key.
|
||||
/// The only public information in this event is who is signing it.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - withRumor: a ``NostrEvent`` that is not signed.
|
||||
/// - toRecipient: the ``PublicKey`` of the receiver of the event.
|
||||
/// - createdAt: the creation timestamp of the seal. Note that this timestamp SHOULD be tweaked to thwart time-analysis attacks. Note that some relays don't serve events dated in the future, so all timestamps SHOULD be in the past. By default, if `createdAt` is not provided, a random timestamp within 2 days in the past will be chosen.
|
||||
/// - keypair: The ``FullKeypair`` to sign with.
|
||||
static func seal(
|
||||
withRumor rumor: NostrEvent,
|
||||
toRecipient recipient: Pubkey,
|
||||
createdAt: UInt32 = UInt32(max(0, Date.now.timeIntervalSince1970 - TimeInterval.random(in: 0...172800))),
|
||||
signedBy fullKeypair: FullKeypair
|
||||
) throws -> NostrEvent? {
|
||||
guard rumor.isRumor else {
|
||||
throw SealEventError.sealSignedEvent
|
||||
}
|
||||
|
||||
let jsonData = try JSONEncoder().encode(rumor)
|
||||
guard let stringifiedJSON = String(data: jsonData, encoding: .utf8) else {
|
||||
throw SealEventError.utf8EncodingFailed
|
||||
}
|
||||
|
||||
let encryptedRumor = try NIP44v2Encryption.encrypt(plaintext: stringifiedJSON, privateKeyA: fullKeypair.privkey, publicKeyB: recipient)
|
||||
return NostrEvent(content: encryptedRumor, keypair: fullKeypair.to_keypair(), kind: NostrKind.seal.rawValue, createdAt: createdAt)
|
||||
}
|
||||
|
||||
/// Unseals the content of this seal event into a decrypted rumor.
|
||||
/// - Parameters:
|
||||
/// - giftWrapEvent: The ``NostrEvent`` gift wrap kind 1059 to unwrap into a seal, and then unseal to reveal the decrypted rumor.
|
||||
/// - privateKey: The `PrivateKey` to decrypt the rumor.
|
||||
/// - Returns: The decrypted ``NostrEvent`` rumor, where its `signature` is absent.
|
||||
static func unsealedRumor(giftWrapEvent: NostrEvent, using privateKey: Privkey) throws -> NostrEvent? {
|
||||
guard let sealEvent = try unwrappedSeal(giftWrapEvent: giftWrapEvent, using: privateKey) else {
|
||||
return nil
|
||||
}
|
||||
return try unsealedRumor(sealEvent: sealEvent, using: privateKey)
|
||||
}
|
||||
|
||||
static func unsealedRumor(
|
||||
sealEvent: NostrEvent,
|
||||
using privateKey: Privkey
|
||||
) throws -> NostrEvent? {
|
||||
guard let unsealedRumor = try? NIP44v2Encryption.decrypt(payload: sealEvent.content, privateKeyA: privateKey, publicKeyB: sealEvent.pubkey) else {
|
||||
throw SealEventError.decryptionFailed
|
||||
}
|
||||
|
||||
guard let data = unsealedRumor.data(using: .utf8) else {
|
||||
throw SealEventError.rumorInvalid
|
||||
}
|
||||
|
||||
guard let dict = (try? JSONSerialization.jsonObject(with: data)) as? [String: Any],
|
||||
let content = dict["content"] as? String,
|
||||
let pubkey = dict["pubkey"] as? String,
|
||||
let author = Pubkey(hex: pubkey),
|
||||
let kind = dict["kind"] as? UInt32,
|
||||
let tags = dict["tags"] as? [[String]],
|
||||
let createdAt = dict["created_at"] as? UInt32,
|
||||
let id = dict["id"] as? String,
|
||||
let noteId = NoteId(hex: id) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
// guard let ev = NostrEvent(content: content, author: author, kind: kind, tags: tags, createdAt: createdAt, id: noteId, sig: Signature(Data())) else {
|
||||
// return nil
|
||||
// }
|
||||
guard let ev = NostrEvent(content: content, keypair: .just_pubkey(author), kind: kind, tags: tags, createdAt: createdAt) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
return ev
|
||||
}
|
||||
}
|
||||
|
||||
extension NostrEvent {
|
||||
var isRumor: Bool {
|
||||
return sig.data == Data(repeating: 0, count: 128)
|
||||
}
|
||||
}
|
||||
|
||||
enum GiftWrapError: Error {
|
||||
case decryptionFailed
|
||||
case jsonDecodingFailed
|
||||
case keypairGenerationFailed
|
||||
case pubkeyInvalid
|
||||
case utf8EncodingFailed
|
||||
case sealInvalid
|
||||
case giftWrapInvalid
|
||||
}
|
||||
|
||||
enum SealEventError: Error {
|
||||
case decryptionFailed
|
||||
case jsonDecodingFailed
|
||||
case pubkeyInvalid
|
||||
case sealSignedEvent
|
||||
case utf8EncodingFailed
|
||||
case sealFailed
|
||||
case rumorInvalid
|
||||
}
|
||||
@@ -13,10 +13,12 @@ enum NostrKind: UInt32, Codable {
|
||||
case metadata = 0
|
||||
case text = 1
|
||||
case contacts = 3
|
||||
case dm = 4
|
||||
case deprecated_dm = 4
|
||||
case delete = 5
|
||||
case boost = 6
|
||||
case like = 7
|
||||
case seal = 13
|
||||
case dm = 14
|
||||
case chat = 42
|
||||
case mute_list = 10000
|
||||
case relay_list = 10002
|
||||
@@ -27,6 +29,7 @@ enum NostrKind: UInt32, Codable {
|
||||
case zap = 9735
|
||||
case zap_request = 9734
|
||||
case highlight = 9802
|
||||
case gift_wrap = 1059
|
||||
case nwc_request = 23194
|
||||
case nwc_response = 23195
|
||||
case http_auth = 27235
|
||||
|
||||
@@ -87,7 +87,7 @@ enum LocalNotificationType: String {
|
||||
switch nostr_kind {
|
||||
case .text:
|
||||
return .mention
|
||||
case .dm:
|
||||
case .deprecated_dm:
|
||||
return .dm
|
||||
case .like:
|
||||
return .like
|
||||
|
||||
@@ -165,6 +165,12 @@ class PostBox {
|
||||
return
|
||||
}
|
||||
|
||||
// Don't add event if it's a NIP-17 direct message kind or a NIP-59 seal event kind to avoid leaking private information.
|
||||
// DMs should be sealed and gift wrapped.
|
||||
if event.known_kind == .dm || event.known_kind == .seal {
|
||||
return
|
||||
}
|
||||
|
||||
let remaining = to ?? pool.our_descriptors.map { $0.url }
|
||||
let after = delay.map { d in Date.now.addingTimeInterval(d) }
|
||||
let posted_ev = PostedEvent(event: event, remaining: remaining, skip_ephemeral: skip_ephemeral, flush_after: after, on_flush: on_flush)
|
||||
|
||||
@@ -108,7 +108,7 @@ struct DMChatView: View, KeyboardReadable {
|
||||
Button(
|
||||
role: .none,
|
||||
action: {
|
||||
send_message()
|
||||
send_message_nip04()
|
||||
}
|
||||
) {
|
||||
Label("", image: "send")
|
||||
@@ -124,7 +124,33 @@ struct DMChatView: View, KeyboardReadable {
|
||||
*/
|
||||
}
|
||||
|
||||
func send_message() {
|
||||
func send_message_nip17() {
|
||||
guard let fullKeypair = damus_state.keypair.to_full() else {
|
||||
return
|
||||
}
|
||||
|
||||
let tags = [["p", pubkey.hex()]]
|
||||
let post_blocks = parse_post_blocks(content: dms.draft)
|
||||
let content = post_blocks
|
||||
.map(\.asString)
|
||||
.joined(separator: "")
|
||||
|
||||
guard let fullKeypair = damus_state.keypair.to_full(),
|
||||
let dm = NIP17.giftWrappedDirectMessage(message: content, senderKeypair: fullKeypair, receiverPubkey: pubkey)
|
||||
else {
|
||||
return
|
||||
}
|
||||
|
||||
dms.draft = ""
|
||||
|
||||
damus_state.nostrNetwork.postbox.send(dm)
|
||||
|
||||
handle_incoming_dm(ev: dm, our_pubkey: damus_state.pubkey, dms: damus_state.dms, prev_events: NewEventsBits())
|
||||
|
||||
end_editing()
|
||||
}
|
||||
|
||||
func send_message_nip04() {
|
||||
let tags = [["p", pubkey.hex()]]
|
||||
let post_blocks = parse_post_blocks(content: dms.draft)
|
||||
let content = post_blocks
|
||||
|
||||
@@ -108,7 +108,7 @@ struct MenuItems: View {
|
||||
Label(NSLocalizedString("Broadcast", comment: "Context menu option for broadcasting the user's note to all of the user's connected relay servers."), image: "globe")
|
||||
}
|
||||
// Mute thread - relocated to below Broadcast, as to move further away from Add Bookmark to prevent accidental muted threads
|
||||
if event.known_kind != .dm {
|
||||
if event.known_kind != .deprecated_dm && event.known_kind != .dm && event.known_kind != .seal {
|
||||
MuteDurationMenu { duration in
|
||||
if let full_keypair = self.damus_state.keypair.to_full(),
|
||||
let new_mutelist_ev = toggle_from_mutelist(keypair: full_keypair, prev: damus_state.mutelist_manager.event, to_toggle: .thread(event.thread_id(), duration?.date_from_now)) {
|
||||
|
||||
@@ -64,7 +64,7 @@ class LoadableNostrEventViewModel: ObservableObject {
|
||||
switch known_kind {
|
||||
case .text, .highlight:
|
||||
return .loaded(route: Route.Thread(thread: ThreadModel(event: ev, damus_state: damus_state)))
|
||||
case .dm:
|
||||
case .deprecated_dm: // FIXME(tyiu)
|
||||
let dm_model = damus_state.dms.lookup_or_create(ev.pubkey)
|
||||
return .loaded(route: Route.DMChat(dms: dm_model))
|
||||
case .like:
|
||||
@@ -74,7 +74,7 @@ class LoadableNostrEventViewModel: ObservableObject {
|
||||
case .zap, .zap_request:
|
||||
guard let zap = await get_zap(from: ev, state: damus_state) else { return .not_found }
|
||||
return .loaded(route: Route.Zaps(target: zap.target))
|
||||
case .contacts, .metadata, .delete, .boost, .chat, .mute_list, .list_deprecated, .draft, .longform, .nwc_request, .nwc_response, .http_auth, .status, .relay_list, .follow_list, .interest_list:
|
||||
case .contacts, .metadata, .delete, .boost, .chat, .mute_list, .list_deprecated, .draft, .longform, .nwc_request, .nwc_response, .http_auth, .status, .relay_list, .follow_list, .interest_list, .dm, .seal, .gift_wrap:
|
||||
return .unknown_or_unsupported_kind
|
||||
}
|
||||
case .naddr(let naddr):
|
||||
|
||||
@@ -40,4 +40,17 @@ final class NostrEventTests: XCTestCase {
|
||||
let urlInContent2 = "https://cdn.nostr.build/i/5c1d3296f66c2630131bf123106486aeaf051ed8466031c0e0532d70b33cddb2.jpg"
|
||||
XCTAssert(testEvent2.content.contains(urlInContent2), "Issue parsing event. Expected to see '\(urlInContent2)' inside \(testEvent2.content)")
|
||||
}
|
||||
|
||||
func testNostrEventWithoutPrivateKey() throws {
|
||||
let event = NostrEvent(
|
||||
content: "Test",
|
||||
keypair: .just_pubkey(test_pubkey),
|
||||
kind: NostrKind.dm.rawValue,
|
||||
tags: []
|
||||
)
|
||||
|
||||
let nonNilEvent = try XCTUnwrap(event)
|
||||
let json = try JSONEncoder().encode(nonNilEvent)
|
||||
print(json)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -174,9 +174,9 @@ class NdbNote: Codable, Equatable, Hashable {
|
||||
let tags = try container.decode([[String]].self, forKey: .tags)
|
||||
let createdAt = try container.decode(UInt32.self, forKey: .created_at)
|
||||
let noteId = try container.decode(NoteId.self, forKey: .id)
|
||||
let signature = try container.decode(Signature.self, forKey: .sig)
|
||||
|
||||
guard let note = NdbNote.init(content: content, author: pubkey, kind: kind, tags: tags, createdAt: createdAt, id: noteId, sig: signature) else {
|
||||
let signature = try? container.decode(Signature.self, forKey: .sig)
|
||||
|
||||
guard let note = NdbNote.init(content: content, author: pubkey, kind: kind, tags: tags, createdAt: createdAt, id: noteId, sig: signature ?? Signature(Data(repeating: 0, count: 128))) else {
|
||||
throw DecodingError.initializationFailed
|
||||
}
|
||||
|
||||
@@ -456,7 +456,7 @@ extension NdbNote {
|
||||
}
|
||||
|
||||
func get_content(_ keypair: Keypair) -> String {
|
||||
if known_kind == .dm {
|
||||
if known_kind == .deprecated_dm {
|
||||
return decrypted(keypair: keypair) ?? "*failed to decrypt content*"
|
||||
}
|
||||
else if known_kind == .highlight {
|
||||
@@ -467,7 +467,7 @@ extension NdbNote {
|
||||
}
|
||||
|
||||
func maybe_get_content(_ keypair: Keypair) -> String? {
|
||||
if known_kind == .dm {
|
||||
if known_kind == .deprecated_dm {
|
||||
return decrypted(keypair: keypair)
|
||||
}
|
||||
|
||||
|
||||
@@ -3628,6 +3628,39 @@ int ndb_builder_finalize(struct ndb_builder *builder, struct ndb_note **note,
|
||||
return total_size;
|
||||
}
|
||||
|
||||
int ndb_builder_finalize_just_pubkey(struct ndb_builder *builder, struct ndb_note **note,
|
||||
unsigned char pubkey[32])
|
||||
{
|
||||
int strings_len = builder->strings.p - builder->strings.start;
|
||||
unsigned char *note_end = builder->note_cur.p + strings_len;
|
||||
int total_size = note_end - builder->note_cur.start;
|
||||
|
||||
// move the strings buffer next to the end of our ndb_note
|
||||
memmove(builder->note_cur.p, builder->strings.start, strings_len);
|
||||
|
||||
// set the strings location
|
||||
builder->note->strings = builder->note_cur.p - builder->note_cur.start;
|
||||
|
||||
// record the total size
|
||||
//builder->note->size = total_size;
|
||||
|
||||
*note = builder->note;
|
||||
|
||||
// use the remaining memory for building our id buffer
|
||||
unsigned char *end = builder->mem.end;
|
||||
unsigned char *start = (unsigned char*)(*note) + total_size;
|
||||
|
||||
ndb_builder_set_pubkey(builder, pubkey);
|
||||
|
||||
if (!ndb_calculate_id(builder->note, start, end - start))
|
||||
return 0;
|
||||
|
||||
// make sure we're aligned as a whole
|
||||
total_size = (total_size + 7) & ~7;
|
||||
assert((total_size % 8) == 0);
|
||||
return total_size;
|
||||
}
|
||||
|
||||
struct ndb_note * ndb_builder_note(struct ndb_builder *builder)
|
||||
{
|
||||
return builder->note;
|
||||
|
||||
@@ -366,6 +366,7 @@ int ndb_ws_event_from_json(const char *json, int len, struct ndb_tce *tce, unsig
|
||||
int ndb_note_from_json(const char *json, int len, struct ndb_note **, unsigned char *buf, int buflen);
|
||||
int ndb_builder_init(struct ndb_builder *builder, unsigned char *buf, int bufsize);
|
||||
int ndb_builder_finalize(struct ndb_builder *builder, struct ndb_note **note, struct ndb_keypair *privkey);
|
||||
int ndb_builder_finalize_just_pubkey(struct ndb_builder *builder, struct ndb_note **note, unsigned char pubkey[32]);
|
||||
int ndb_builder_set_content(struct ndb_builder *builder, const char *content, int len);
|
||||
void ndb_builder_set_created_at(struct ndb_builder *builder, uint64_t created_at);
|
||||
void ndb_builder_set_sig(struct ndb_builder *builder, unsigned char *sig);
|
||||
|
||||
Reference in New Issue
Block a user