Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Change NIP-04 direct message and encryption naming to avoid ambiguity with NIP-17 #158

Merged
merged 5 commits into from
May 28, 2024
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Sources/NostrSDK/EventCreating.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,4 @@ enum EventCreatingError: Error {
case invalidInput
}

public protocol EventCreating: DirectMessageEncrypting, RelayURLValidating {}
public protocol EventCreating: LegacyDirectMessageEncrypting, RelayURLValidating {}
80 changes: 41 additions & 39 deletions Sources/NostrSDK/EventKind.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,10 @@ public enum EventKind: RawRepresentable, CaseIterable, Codable, Equatable, Hasha

/// This kind of event should have a recipient pubkey tag.
///
/// See [NIP-04 - Direct Messages](https://github.com/nostr-protocol/nips/blob/master/04.md)
case directMessage
/// See [NIP-04 - Encrypted Direct Message](https://github.com/nostr-protocol/nips/blob/master/04.md)
/// > Warning: Deprecated in favor of [NIP-17 - Private Direct Messages](https://github.com/nostr-protocol/nips/blob/master/17.md).
@available(*, deprecated, message: "Deprecated in favor of NIP-17 - Private Direct Messages.")
case legacyEncryptedDirectMessage

/// This kind of event indicates that the author requests that the events in the included
/// tags should be deleted.
Expand Down Expand Up @@ -110,7 +112,7 @@ public enum EventKind: RawRepresentable, CaseIterable, Codable, Equatable, Hasha
.setMetadata,
.textNote,
.followList,
.directMessage,
.legacyEncryptedDirectMessage,
.deletion,
.repost,
.reaction,
Expand All @@ -137,48 +139,48 @@ public enum EventKind: RawRepresentable, CaseIterable, Codable, Equatable, Hasha

public var rawValue: RawValue {
switch self {
case .setMetadata: return 0
case .textNote: return 1
case .followList: return 3
case .directMessage: return 4
case .deletion: return 5
case .repost: return 6
case .reaction: return 7
case .genericRepost: return 16
case .report: return 1984
case .muteList: return 10000
case .bookmarksList: return 10003
case .authentication: return 22242
case .longformContent: return 30023
case .dateBasedCalendarEvent: return 31922
case .timeBasedCalendarEvent: return 31923
case .calendar: return 31924
case .calendarEventRSVP: return 31925
case let .unknown(value): return value
case .setMetadata: return 0
case .textNote: return 1
case .followList: return 3
case .legacyEncryptedDirectMessage: return 4
case .deletion: return 5
case .repost: return 6
case .reaction: return 7
case .genericRepost: return 16
case .report: return 1984
case .muteList: return 10000
case .bookmarksList: return 10003
case .authentication: return 22242
case .longformContent: return 30023
case .dateBasedCalendarEvent: return 31922
case .timeBasedCalendarEvent: return 31923
case .calendar: return 31924
case .calendarEventRSVP: return 31925
case let .unknown(value): return value
}
}

/// The ``NostrEvent`` subclass associated with the kind.
public var classForKind: NostrEvent.Type {
switch self {
case .setMetadata: return SetMetadataEvent.self
case .textNote: return TextNoteEvent.self
case .followList: return FollowListEvent.self
case .directMessage: return DirectMessageEvent.self
case .deletion: return DeletionEvent.self
case .repost: return TextNoteRepostEvent.self
case .reaction: return ReactionEvent.self
case .genericRepost: return GenericRepostEvent.self
case .report: return ReportEvent.self
case .muteList: return MuteListEvent.self
case .bookmarksList: return BookmarksListEvent.self
case .authentication: return AuthenticationEvent.self
case .longformContent: return LongformContentEvent.self
case .dateBasedCalendarEvent: return DateBasedCalendarEvent.self
case .timeBasedCalendarEvent: return TimeBasedCalendarEvent.self
case .calendar: return CalendarListEvent.self
case .calendarEventRSVP: return CalendarEventRSVP.self
case .unknown: return NostrEvent.self
case .setMetadata: return SetMetadataEvent.self
case .textNote: return TextNoteEvent.self
case .followList: return FollowListEvent.self
case .legacyEncryptedDirectMessage: return LegacyEncryptedDirectMessageEvent.self
case .deletion: return DeletionEvent.self
case .repost: return TextNoteRepostEvent.self
case .reaction: return ReactionEvent.self
case .genericRepost: return GenericRepostEvent.self
case .report: return ReportEvent.self
case .muteList: return MuteListEvent.self
case .bookmarksList: return BookmarksListEvent.self
case .authentication: return AuthenticationEvent.self
case .longformContent: return LongformContentEvent.self
case .dateBasedCalendarEvent: return DateBasedCalendarEvent.self
case .timeBasedCalendarEvent: return TimeBasedCalendarEvent.self
case .calendar: return CalendarListEvent.self
case .calendarEventRSVP: return CalendarEventRSVP.self
case .unknown: return NostrEvent.self
}
}

Expand Down
6 changes: 3 additions & 3 deletions Sources/NostrSDK/Events/BookmarksListEvent.swift
Original file line number Diff line number Diff line change
Expand Up @@ -141,9 +141,9 @@ public extension EventCreating {
let rawPrivateTags = privateTags.map { $0.raw }
if let unencryptedData = try? JSONSerialization.data(withJSONObject: rawPrivateTags),
let unencryptedContent = String(data: unencryptedData, encoding: .utf8) {
encryptedContent = try encrypt(content: unencryptedContent,
privateKey: keypair.privateKey,
publicKey: keypair.publicKey)
encryptedContent = try legacyEncrypt(content: unencryptedContent,
privateKey: keypair.privateKey,
publicKey: keypair.publicKey)
}
}

Expand Down
60 changes: 0 additions & 60 deletions Sources/NostrSDK/Events/DirectMessageEvent.swift

This file was deleted.

64 changes: 64 additions & 0 deletions Sources/NostrSDK/Events/LegacyEncryptedDirectMessageEvent.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
//
// LegacyEncryptedDirectMessageEvent.swift
//
//
// Created by Joel Klabo on 8/10/23.
//

import Foundation

/// An event that contains an encrypted message.
///
/// > Note: [NIP-04 - Encrypted Direct Message](https://github.com/nostr-protocol/nips/blob/master/04.md)
/// > Warning: Deprecated in favor of [NIP-17 - Private Direct Messages](https://github.com/nostr-protocol/nips/blob/master/17.md).
@available(*, deprecated, message: "Deprecated in favor of NIP-17 - Private Direct Messages.")
public final class LegacyEncryptedDirectMessageEvent: NostrEvent, LegacyDirectMessageEncrypting {

public required init(from decoder: Decoder) throws {
try super.init(from: decoder)
}

@available(*, unavailable, message: "This initializer is unavailable for this class.")
override init(kind: EventKind, content: String, tags: [Tag] = [], createdAt: Int64 = Int64(Date.now.timeIntervalSince1970), signedBy keypair: Keypair) throws {
try super.init(kind: kind, content: content, tags: tags, createdAt: createdAt, signedBy: keypair)
}

init(content: String, tags: [Tag] = [], createdAt: Int64 = Int64(Date.now.timeIntervalSince1970), signedBy keypair: Keypair) throws {
try super.init(kind: .legacyEncryptedDirectMessage, content: content, tags: tags, createdAt: createdAt, signedBy: keypair)
}

/// Returns decrypted content from Event given a `privateKey`
public func decryptedContent(using privateKey: PrivateKey) throws -> String {
let recipient = tags.first { tag in
tag.name == TagName.pubkey.rawValue
}

guard let recipientPublicKeyHex = recipient?.value, let recipientPublicKey = PublicKey(hex: recipientPublicKeyHex) else {
throw LegacyDirectMessageEncryptingError.pubkeyInvalid
}

return try legacyDecrypt(encryptedContent: content, privateKey: privateKey, publicKey: recipientPublicKey)
}
}

public extension EventCreating {

/// Creates a ``LegacyEncryptedDirectMessageEvent`` (kind 4) and signs it with the provided ``Keypair``.
/// - Parameters:
/// - content: The content of the text note.
/// - toRecipient: The PublicKey of the recipient.
/// - keypair: The Keypair to sign with.
/// - Returns: The signed ``LegacyEncryptedDirectMessageEvent``.
///
/// See [NIP-04 - Encrypted Direct Message](https://github.com/nostr-protocol/nips/blob/master/04.md)
/// > Warning: Deprecated in favor of [NIP-17 - Private Direct Messages](https://github.com/nostr-protocol/nips/blob/master/17.md).
@available(*, deprecated, message: "Deprecated in favor of NIP-17 - Private Direct Messages.")
func legacyEncryptedDirectMessage(withContent content: String, toRecipient pubkey: PublicKey, signedBy keypair: Keypair) throws -> LegacyEncryptedDirectMessageEvent {
guard let encryptedMessage = try? legacyEncrypt(content: content, privateKey: keypair.privateKey, publicKey: pubkey) else {
throw EventCreatingError.invalidInput
}

let recipientTag = Tag.pubkey(pubkey.hex)
return try LegacyEncryptedDirectMessageEvent(content: encryptedMessage, tags: [recipientTag], signedBy: keypair)
}
}
6 changes: 3 additions & 3 deletions Sources/NostrSDK/Events/MuteListEvent.swift
Original file line number Diff line number Diff line change
Expand Up @@ -100,9 +100,9 @@ public extension EventCreating {
let rawPrivateTags = privateTags.map { $0.raw }
if let unencryptedData = try? JSONSerialization.data(withJSONObject: rawPrivateTags),
let unencryptedContent = String(data: unencryptedData, encoding: .utf8) {
encryptedContent = try encrypt(content: unencryptedContent,
privateKey: keypair.privateKey,
publicKey: keypair.publicKey)
encryptedContent = try legacyEncrypt(content: unencryptedContent,
privateKey: keypair.privateKey,
publicKey: keypair.publicKey)
}
}

Expand Down
4 changes: 2 additions & 2 deletions Sources/NostrSDK/Events/Tags/PrivateTagInterpreting.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

import Foundation

public protocol PrivateTagInterpreting: DirectMessageEncrypting {}
public protocol PrivateTagInterpreting: LegacyDirectMessageEncrypting {}
public extension PrivateTagInterpreting {

/// The private tags encrypted in the content of the event.
Expand All @@ -16,7 +16,7 @@ public extension PrivateTagInterpreting {
/// - Parameter keypair: The keypair to use to decrypt the content.
/// - Returns: The private tags.
func privateTags(from content: String, withName tagName: TagName? = nil, using keypair: Keypair) -> [Tag] {
guard let decryptedContent = try? decrypt(encryptedContent: content, privateKey: keypair.privateKey, publicKey: keypair.publicKey),
guard let decryptedContent = try? legacyDecrypt(encryptedContent: content, privateKey: keypair.privateKey, publicKey: keypair.publicKey),
let jsonData = decryptedContent.data(using: .utf8) else {
return []
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//
// DirectMessageEncrypting.swift
//
// LegacyDirectMessageEncrypting.swift
//
//
// Created by Joel Klabo on 8/10/23.
//
Expand All @@ -10,16 +10,16 @@ import secp256k1
import CommonCrypto
import CryptoKit

public enum DirectMessageEncryptingError: Error {
public enum LegacyDirectMessageEncryptingError: Error {
case pubkeyInvalid
case unsuccessfulExponentiation
case encryptionError
case decryptionError
case missingValue
}

public protocol DirectMessageEncrypting {}
public extension DirectMessageEncrypting {
public protocol LegacyDirectMessageEncrypting {}
public extension LegacyDirectMessageEncrypting {

/// Produces a `String` containing `content` that has been encrypted using a sender's `privateKey` and a recipient's `publicKey`.
/// This function can `throw` in the case of a failure to create a shared secret, a failure to successfully encrypt, or an invalid `publicKey`.
Expand All @@ -29,14 +29,16 @@ public extension DirectMessageEncrypting {
/// - privateKey: The private key of the sender.
/// - publicKey: The public key of the intended recipient.
/// - Returns: Encrypted content.
func encrypt(content: String, privateKey: PrivateKey, publicKey: PublicKey) throws -> String {
/// > Warning: Deprecated in favor of [NIP-44](https://github.com/nostr-protocol/nips/blob/master/44.md) encryption and [NIP-59](https://github.com/nostr-protocol/nips/blob/master/59.md) seals and gift wraps..
@available(*, deprecated, message: "Deprecated in favor of NIP-44 encryption and NIP-59 seals and gift wraps.")
func legacyEncrypt(content: String, privateKey: PrivateKey, publicKey: PublicKey) throws -> String {

let sharedSecret = try getSharedSecret(privateKey: privateKey, recipient: publicKey)

let iv = Data.randomBytes(count: 16).bytes
let utf8Content = Data(content.utf8).bytes
guard let encryptedMessage = AESEncrypt(data: utf8Content, iv: iv, sharedSecret: sharedSecret) else {
throw DirectMessageEncryptingError.encryptionError
throw LegacyDirectMessageEncryptingError.encryptionError
}

return encodeDMBase64(content: encryptedMessage.bytes, iv: iv)
Expand All @@ -50,32 +52,34 @@ public extension DirectMessageEncrypting {
/// - privateKey: The private key of the receiver.
/// - publicKey: The public key of the sender.
/// - Returns: The un-encrypted message.
func decrypt(encryptedContent message: String, privateKey: PrivateKey, publicKey: PublicKey) throws -> String {
/// > Warning: Deprecated in favor of [NIP-44](https://github.com/nostr-protocol/nips/blob/master/44.md) encryption and [NIP-59](https://github.com/nostr-protocol/nips/blob/master/59.md) seals and gift wraps..
@available(*, deprecated, message: "Deprecated in favor of NIP-44 encryption and NIP-59 seals and gift wraps.")
func legacyDecrypt(encryptedContent message: String, privateKey: PrivateKey, publicKey: PublicKey) throws -> String {
guard let sharedSecret = try? getSharedSecret(privateKey: privateKey, recipient: publicKey) else {
throw EventCreatingError.invalidInput
}

let sections = Array(message.split(separator: "?"))

if sections.count != 2 {
throw DirectMessageEncryptingError.decryptionError
throw LegacyDirectMessageEncryptingError.decryptionError
}

guard let encryptedContent = sections.first,
let encryptedContentData = Data(base64Encoded: String(encryptedContent)) else {
throw DirectMessageEncryptingError.decryptionError
throw LegacyDirectMessageEncryptingError.decryptionError
}

guard let ivContent = sections.last else {
throw DirectMessageEncryptingError.decryptionError
throw LegacyDirectMessageEncryptingError.decryptionError
}

let ivContentTrimmed = ivContent.dropFirst(3)

guard let ivContentData = Data(base64Encoded: String(ivContentTrimmed)),
let decryptedContentData = AESDecrypt(data: encryptedContentData.bytes, iv: ivContentData.bytes, sharedSecret: sharedSecret),
let decryptedMessage = String(data: decryptedContentData, encoding: .utf8) else {
throw DirectMessageEncryptingError.decryptionError
throw LegacyDirectMessageEncryptingError.decryptionError
}

return decryptedMessage
Expand All @@ -98,7 +102,7 @@ public extension DirectMessageEncrypting {
private func parsePublicKey(from bytes: [UInt8]) throws -> secp256k1_pubkey {
var recipientPublicKey = secp256k1_pubkey()
guard secp256k1_ec_pubkey_parse(secp256k1.Context.rawRepresentation, &recipientPublicKey, bytes, bytes.count) != 0 else {
throw DirectMessageEncryptingError.pubkeyInvalid
throw LegacyDirectMessageEncryptingError.pubkeyInvalid
}
return recipientPublicKey
}
Expand All @@ -110,7 +114,7 @@ public extension DirectMessageEncrypting {
memcpy(output, x32, 32)
return 1
}, nil) != 0 else {
throw DirectMessageEncryptingError.unsuccessfulExponentiation
throw LegacyDirectMessageEncryptingError.unsuccessfulExponentiation
}
return sharedSecret
}
Expand Down
Loading
Loading