Skip to content

Commit

Permalink
Merge pull request #1714 from planetary-social/bdm/decrypt-private-tags
Browse files Browse the repository at this point in the history
added support for decrypting private tags in kind 30000 lists
  • Loading branch information
bryanmontz authored Dec 29, 2024
2 parents 89c43b2 + 8e8a309 commit 7c14d54
Show file tree
Hide file tree
Showing 13 changed files with 320 additions and 10 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Added feed source customizer drop-down view. [#102](https://github.com/verse-pbc/issues/issues/102)
- Make feed source selector work.
- Add empty state for lists/relays drop-down.
- Added support for decrypting private tags in kind 30000 lists.

### Internal Changes
- Upgraded to Xcode 16. [#1570](https://github.com/planetary-social/nos/issues/1570)
Expand Down
8 changes: 7 additions & 1 deletion Nos.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,7 @@
50089A0D2C97182200834588 /* CurrentUser+PublishEvents.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50089A0B2C97182200834588 /* CurrentUser+PublishEvents.swift */; };
50089A172C98678600834588 /* View+ListRowGradientBackground.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50089A162C98678600834588 /* View+ListRowGradientBackground.swift */; };
501728B42D16EFB000CF2A07 /* FeedPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 501728B32D16EFAC00CF2A07 /* FeedPicker.swift */; };
5022F9462D2186380012FF4B /* follow_set_private.json in Resources */ = {isa = PBXBuildFile; fileRef = 5022F9452D2186300012FF4B /* follow_set_private.json */; };
502B6C3D2C9462A400446316 /* PushNotificationRegistrar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 502B6C3C2C9462A400446316 /* PushNotificationRegistrar.swift */; };
503CA9532D19ACCC00805EF8 /* HorizontalLine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 503CA9522D19ACC800805EF8 /* HorizontalLine.swift */; };
503CA9792D19C39F00805EF8 /* FeedCustomizerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 503CA9782D19C39800805EF8 /* FeedCustomizerView.swift */; };
Expand Down Expand Up @@ -763,6 +764,8 @@
50089A0B2C97182200834588 /* CurrentUser+PublishEvents.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CurrentUser+PublishEvents.swift"; sourceTree = "<group>"; };
50089A162C98678600834588 /* View+ListRowGradientBackground.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "View+ListRowGradientBackground.swift"; sourceTree = "<group>"; };
501728B32D16EFAC00CF2A07 /* FeedPicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedPicker.swift; sourceTree = "<group>"; };
5022F9452D2186300012FF4B /* follow_set_private.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = follow_set_private.json; sourceTree = "<group>"; };
5022F9472D2188650012FF4B /* Nos 23.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "Nos 23.xcdatamodel"; sourceTree = "<group>"; };
502B6C3C2C9462A400446316 /* PushNotificationRegistrar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PushNotificationRegistrar.swift; sourceTree = "<group>"; };
503CA9522D19ACC800805EF8 /* HorizontalLine.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HorizontalLine.swift; sourceTree = "<group>"; };
503CA9782D19C39800805EF8 /* FeedCustomizerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedCustomizerView.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -1271,6 +1274,7 @@
C9BD91882B61BBEF00FDA083 /* bad_contact_list.json */,
0350F1162C0A47B20024CC15 /* contact_list.json */,
03C853C52D03A50900164D6C /* follow_set.json */,
5022F9452D2186300012FF4B /* follow_set_private.json */,
03FFCA582D075E2800D6F0F1 /* follow_set_updated.json */,
039C96282C48321E00A8EB39 /* long_form_data.json */,
C95057B02CC6986E0024EC9C /* mute_list_2.json */,
Expand Down Expand Up @@ -2286,6 +2290,7 @@
3AEABEFE2B2BF850001BC933 /* ImagePicker.xcstrings in Resources */,
0350F1172C0A47B20024CC15 /* contact_list.json in Resources */,
C944024D2C5BE6A600834568 /* Assets.xcassets in Resources */,
5022F9462D2186380012FF4B /* follow_set_private.json in Resources */,
03C853C62D03A50900164D6C /* follow_set.json in Resources */,
C987F83729BA951E00B44E7A /* ClarityCity-ExtraBold.otf in Resources */,
C987F83329BA951E00B44E7A /* ClarityCity-ExtraLight.otf in Resources */,
Expand Down Expand Up @@ -3917,6 +3922,7 @@
C936B4572A4C7B7C00DF1EB9 /* Nos.xcdatamodeld */ = {
isa = XCVersionGroup;
children = (
5022F9472D2188650012FF4B /* Nos 23.xcdatamodel */,
503CAB7B2D1DA6DB00805EF8 /* Nos 22.xcdatamodel */,
0303B11E2D0257D400077929 /* Nos 21.xcdatamodel */,
2D3C71A52CEE6F7100625BCB /* Nos 20.xcdatamodel */,
Expand All @@ -3932,7 +3938,7 @@
C9C547562A4F1D1A006B0741 /* Nos 9.xcdatamodel */,
5BFF66AF2A4B55FC00AA79DD /* Nos 10.xcdatamodel */,
);
currentVersion = 503CAB7B2D1DA6DB00805EF8 /* Nos 22.xcdatamodel */;
currentVersion = 5022F9472D2188650012FF4B /* Nos 23.xcdatamodel */;
path = Nos.xcdatamodeld;
sourceTree = "<group>";
versionGroupType = wrapper.xcdatamodel;
Expand Down
33 changes: 32 additions & 1 deletion Nos/Models/CoreData/AuthorList+CoreDataClass.swift
Original file line number Diff line number Diff line change
@@ -1,10 +1,23 @@
import Foundation
import CoreData
import NostrSDK

/// This class is needed only as a utility. The protocol functions only work on instances,
/// (as opposed to classes in static functions).
fileprivate final class TagInterpreter: PrivateTagInterpreting, DirectMessageEncrypting {
}

extension Keypair {
static func withNosKeyPair(_ keyPair: KeyPair) -> Keypair? {
Keypair(nsec: keyPair.nsec)
}
}

@objc(AuthorList)
public class AuthorList: Event {
static func createOrUpdate(
from jsonEvent: JSONEvent,
keyPair: KeyPair? = nil,
in context: NSManagedObjectContext
) throws -> AuthorList {
guard jsonEvent.kind == EventKind.followSet.rawValue else { throw AuthorListError.invalidKind }
Expand Down Expand Up @@ -45,7 +58,21 @@ public class AuthorList: Event {
authorList.listDescription = tag[safe: 1]
}
}


if !jsonEvent.content.isEmpty,
let keyPair,
let nostrSDKKeypair = Keypair.withNosKeyPair(keyPair) {
let authorIDs = TagInterpreter().valuesForPrivateTags(
from: jsonEvent.content,
withName: .pubkey,
using: nostrSDKKeypair
)
for authorID in authorIDs {
let author = try Author.findOrCreate(by: authorID, context: context)
authorList.addToPrivateAuthors(author)
}
}

return authorList
}

Expand All @@ -65,4 +92,8 @@ public class AuthorList: Event {
fetchRequest.fetchLimit = 1
return fetchRequest
}

var allAuthors: Set<Author> {
authors.union(privateAuthors)
}
}
15 changes: 15 additions & 0 deletions Nos/Models/CoreData/Generated/AuthorList+CoreDataProperties.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ extension AuthorList {
/// The set of unique authors in this list.
@NSManaged public var authors: Set<Author>

/// The set of privately listed unique authors.
@NSManaged public var privateAuthors: Set<Author>

/// Whether or not this list should be visible in the ``FeedPicker``.
@NSManaged public var isFeedEnabled: Bool
}
Expand All @@ -41,4 +44,16 @@ extension AuthorList {

@objc(removeAuthors:)
@NSManaged public func removeFromAuthors(_ values: NSSet)

@objc(addPrivateAuthorsObject:)
@NSManaged public func addToPrivateAuthors(_ value: Author)

@objc(removePrivateAuthorsObject:)
@NSManaged public func removeFromPrivateAuthors(_ value: Author)

@objc(addPrivateAuthors:)
@NSManaged public func addToPrivateAuthors(_ values: NSSet)

@objc(removePrivateAuthors:)
@NSManaged public func removeFromPrivateAuthors(_ values: NSSet)
}
2 changes: 1 addition & 1 deletion Nos/Models/CoreData/Nos.xcdatamodeld/.xccurrentversion
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,6 @@
<plist version="1.0">
<dict>
<key>_XCCurrentVersionName</key>
<string>Nos 22.xcdatamodel</string>
<string>Nos 23.xcdatamodel</string>
</dict>
</plist>
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>_XCCurrentVersionName</key>
<string>Nos.xcdatamodel</string>
</dict>
</plist>
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="21513" systemVersion="22D68" minimumToolsVersion="Automatic" sourceLanguage="Swift" userDefinedModelVersionIdentifier="">
<entity name="Author" representedClassName=".Author" syncable="YES" codeGenerationType="category">
<attribute name="about" optional="YES" attributeType="String"/>
<attribute name="displayName" optional="YES" attributeType="String"/>
<attribute name="hexadecimalPublicKey" attributeType="String"/>
<attribute name="lastUpdatedContactList" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
<attribute name="lastUpdatedMetadata" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
<attribute name="muted" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="name" optional="YES" attributeType="String"/>
<attribute name="nip05" optional="YES" attributeType="String"/>
<attribute name="profilePhotoURL" optional="YES" attributeType="URI"/>
<attribute name="rawMetadata" optional="YES" attributeType="Binary"/>
<relationship name="events" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="Event" inverseName="author" inverseEntity="Event"/>
<relationship name="followers" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="Follow" inverseName="destination" inverseEntity="Follow"/>
<relationship name="follows" optional="YES" toMany="YES" deletionRule="Cascade" destinationEntity="Follow" inverseName="source" inverseEntity="Follow"/>
<relationship name="relays" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="Relay"/>
</entity>
<entity name="AuthorReference" representedClassName="AuthorReference" syncable="YES" codeGenerationType="category">
<attribute name="pubkey" optional="YES" attributeType="String"/>
<attribute name="recommendedRelayUrl" optional="YES" attributeType="String"/>
<relationship name="event" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Event" inverseName="authorReferences" inverseEntity="Event"/>
</entity>
<entity name="Event" representedClassName=".Event" syncable="YES" codeGenerationType="category">
<attribute name="allTags" optional="YES" attributeType="Transformable" valueTransformerName="NSSecureUnarchiveFromDataTransformerName"/>
<attribute name="content" optional="YES" attributeType="String"/>
<attribute name="createdAt" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
<attribute name="identifier" attributeType="String"/>
<attribute name="kind" optional="YES" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="sendAttempts" optional="YES" attributeType="Integer 16" usesScalarValueType="YES"/>
<attribute name="signature" optional="YES" attributeType="String"/>
<relationship name="author" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Author" inverseName="events" inverseEntity="Author"/>
<relationship name="authorReferences" optional="YES" toMany="YES" deletionRule="Nullify" ordered="YES" destinationEntity="AuthorReference" inverseName="event" inverseEntity="AuthorReference"/>
<relationship name="deletedOn" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="Relay"/>
<relationship name="eventReferences" optional="YES" toMany="YES" deletionRule="Nullify" ordered="YES" destinationEntity="EventReference" inverseName="referencingEvent" inverseEntity="EventReference"/>
<relationship name="publishedTo" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="Relay"/>
<relationship name="referencingEvents" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="EventReference" inverseName="referencedEvent" inverseEntity="EventReference"/>
</entity>
<entity name="EventReference" representedClassName="EventReference" syncable="YES" codeGenerationType="category">
<attribute name="eventId" optional="YES" attributeType="String"/>
<attribute name="marker" optional="YES" attributeType="String"/>
<attribute name="recommendedRelayUrl" optional="YES" attributeType="String"/>
<relationship name="referencedEvent" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Event" inverseName="referencingEvents" inverseEntity="Event"/>
<relationship name="referencingEvent" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Event" inverseName="eventReferences" inverseEntity="Event"/>
</entity>
<entity name="Follow" representedClassName=".Follow" syncable="YES" codeGenerationType="category">
<attribute name="lastUpdated" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
<attribute name="petName" optional="YES" attributeType="String"/>
<relationship name="destination" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Author" inverseName="followers" inverseEntity="Author"/>
<relationship name="source" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Author" inverseName="follows" inverseEntity="Author"/>
</entity>
<entity name="Relay" representedClassName=".Relay" syncable="YES" codeGenerationType="category">
<attribute name="address" attributeType="String"/>
<attribute name="createdAt" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
</entity>
</model>
Loading

0 comments on commit 7c14d54

Please sign in to comment.