Skip to content
Open
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
72 changes: 72 additions & 0 deletions damus.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,32 @@
objects = {

/* Begin PBXBuildFile section */
BL0SS0M0000000000000011 /* BlossomTypes.swift in Sources */ = {isa = PBXBuildFile; fileRef = BL0SS0M0000000000000001 /* BlossomTypes.swift */; };
BL0SS0M0000000000000012 /* BlossomTypes.swift in Sources */ = {isa = PBXBuildFile; fileRef = BL0SS0M0000000000000001 /* BlossomTypes.swift */; };
BL0SS0M0000000000000013 /* BlossomTypes.swift in Sources */ = {isa = PBXBuildFile; fileRef = BL0SS0M0000000000000001 /* BlossomTypes.swift */; };
BL0SS0M0000000000000021 /* BlossomAuth.swift in Sources */ = {isa = PBXBuildFile; fileRef = BL0SS0M0000000000000002 /* BlossomAuth.swift */; };
BL0SS0M0000000000000022 /* BlossomAuth.swift in Sources */ = {isa = PBXBuildFile; fileRef = BL0SS0M0000000000000002 /* BlossomAuth.swift */; };
BL0SS0M0000000000000023 /* BlossomAuth.swift in Sources */ = {isa = PBXBuildFile; fileRef = BL0SS0M0000000000000002 /* BlossomAuth.swift */; };
BL0SS0M0000000000000031 /* BlossomUploader.swift in Sources */ = {isa = PBXBuildFile; fileRef = BL0SS0M0000000000000003 /* BlossomUploader.swift */; };
BL0SS0M0000000000000032 /* BlossomUploader.swift in Sources */ = {isa = PBXBuildFile; fileRef = BL0SS0M0000000000000003 /* BlossomUploader.swift */; };
BL0SS0M0000000000000033 /* BlossomUploader.swift in Sources */ = {isa = PBXBuildFile; fileRef = BL0SS0M0000000000000003 /* BlossomUploader.swift */; };
BL0SS0M0000000000000041 /* BlossomServerList.swift in Sources */ = {isa = PBXBuildFile; fileRef = BL0SS0M0000000000000004 /* BlossomServerList.swift */; };
BL0SS0M0000000000000042 /* BlossomServerList.swift in Sources */ = {isa = PBXBuildFile; fileRef = BL0SS0M0000000000000004 /* BlossomServerList.swift */; };
BL0SS0M0000000000000043 /* BlossomServerList.swift in Sources */ = {isa = PBXBuildFile; fileRef = BL0SS0M0000000000000004 /* BlossomServerList.swift */; };
BL0SS0M0000000000000051 /* BlossomServerListManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = BL0SS0M0000000000000005 /* BlossomServerListManager.swift */; };
BL0SS0M0000000000000052 /* BlossomServerListManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = BL0SS0M0000000000000005 /* BlossomServerListManager.swift */; };
BL0SS0M0000000000000053 /* BlossomServerListManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = BL0SS0M0000000000000005 /* BlossomServerListManager.swift */; };
BL0SS0M0000000000000061 /* MediaServerSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BL0SS0M0000000000000006 /* MediaServerSettingsView.swift */; };
BL0SS0M0000000000000062 /* MediaServerSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BL0SS0M0000000000000006 /* MediaServerSettingsView.swift */; };
BL0SS0M0000000000000063 /* MediaServerSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BL0SS0M0000000000000006 /* MediaServerSettingsView.swift */; };
BL0SS0M0000000000000071 /* AddBlossomServerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BL0SS0M0000000000000007 /* AddBlossomServerView.swift */; };
BL0SS0M0000000000000072 /* AddBlossomServerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BL0SS0M0000000000000007 /* AddBlossomServerView.swift */; };
BL0SS0M0000000000000073 /* AddBlossomServerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BL0SS0M0000000000000007 /* AddBlossomServerView.swift */; };
BL0SS0M0000000000000014 /* BlossomTypes.swift in Sources */ = {isa = PBXBuildFile; fileRef = BL0SS0M0000000000000001 /* BlossomTypes.swift */; };
BL0SS0M0000000000000024 /* BlossomAuth.swift in Sources */ = {isa = PBXBuildFile; fileRef = BL0SS0M0000000000000002 /* BlossomAuth.swift */; };
BL0SS0M0000000000000034 /* BlossomUploader.swift in Sources */ = {isa = PBXBuildFile; fileRef = BL0SS0M0000000000000003 /* BlossomUploader.swift */; };
BL0SS0M0000000000000044 /* BlossomServerList.swift in Sources */ = {isa = PBXBuildFile; fileRef = BL0SS0M0000000000000004 /* BlossomServerList.swift */; };
BL0SS0M0000000000000054 /* BlossomServerListManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = BL0SS0M0000000000000005 /* BlossomServerListManager.swift */; };
0E8A4BB72AE4359200065E81 /* NostrFilter+Hashable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E8A4BB62AE4359200065E81 /* NostrFilter+Hashable.swift */; };
2710433D2E6BFE340005C3B0 /* PostingTimelineSwitcherView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2710433C2E6BFE2A0005C3B0 /* PostingTimelineSwitcherView.swift */; };
2710433E2E6BFE340005C3B0 /* PostingTimelineSwitcherView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2710433C2E6BFE2A0005C3B0 /* PostingTimelineSwitcherView.swift */; };
Expand Down Expand Up @@ -2820,6 +2846,13 @@
D7DB1FF22D5AC5E400CF06DA /* LICENSES */ = {isa = PBXFileReference; lastKnownFileType = text; path = LICENSES; sourceTree = "<group>"; };
D7DB93042D66A43B00DA1EE5 /* Undistractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Undistractor.swift; sourceTree = "<group>"; };
D7DB93092D69485A00DA1EE5 /* NIP65.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NIP65.swift; sourceTree = "<group>"; };
BL0SS0M0000000000000001 /* BlossomTypes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlossomTypes.swift; sourceTree = "<group>"; };
BL0SS0M0000000000000002 /* BlossomAuth.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlossomAuth.swift; sourceTree = "<group>"; };
BL0SS0M0000000000000003 /* BlossomUploader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlossomUploader.swift; sourceTree = "<group>"; };
BL0SS0M0000000000000004 /* BlossomServerList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlossomServerList.swift; sourceTree = "<group>"; };
BL0SS0M0000000000000005 /* BlossomServerListManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlossomServerListManager.swift; sourceTree = "<group>"; };
BL0SS0M0000000000000006 /* MediaServerSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaServerSettingsView.swift; sourceTree = "<group>"; };
BL0SS0M0000000000000007 /* AddBlossomServerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddBlossomServerView.swift; sourceTree = "<group>"; };
D7DEEF2E2A8C021E00E0C99F /* NostrEventTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NostrEventTests.swift; sourceTree = "<group>"; };
D7DF58312DFCF18800E9AD28 /* SendPaymentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SendPaymentView.swift; sourceTree = "<group>"; };
D7E5B2D22EA0187B00CF47AC /* StreamPipelineDiagnostics.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StreamPipelineDiagnostics.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -4080,6 +4113,7 @@
5C78A7752E22F84A00CF177D /* Core */ = {
isa = PBXGroup;
children = (
BL0SS0M0000000000000000 /* Blossom */,
5C78A7BD2E306D6000CF177D /* Storage */,
5C78A77C2E22FE7100CF177D /* Networking */,
5C78A7782E22FAE700CF177D /* DIPs */,
Expand Down Expand Up @@ -4114,6 +4148,18 @@
path = NIP98;
sourceTree = "<group>";
};
BL0SS0M0000000000000000 /* Blossom */ = {
isa = PBXGroup;
children = (
BL0SS0M0000000000000001 /* BlossomTypes.swift */,
BL0SS0M0000000000000002 /* BlossomAuth.swift */,
BL0SS0M0000000000000003 /* BlossomUploader.swift */,
BL0SS0M0000000000000004 /* BlossomServerList.swift */,
BL0SS0M0000000000000005 /* BlossomServerListManager.swift */,
);
path = Blossom;
sourceTree = "<group>";
};
5C78A7782E22FAE700CF177D /* DIPs */ = {
isa = PBXGroup;
children = (
Expand Down Expand Up @@ -4433,6 +4479,8 @@
E4FA1C022A24BB7F00482697 /* SearchSettingsView.swift */,
5053ACA62A56DF3B00851AE3 /* DeveloperSettingsView.swift */,
D7FD12252BD345A700CF195B /* FirstAidSettingsView.swift */,
BL0SS0M0000000000000006 /* MediaServerSettingsView.swift */,
BL0SS0M0000000000000007 /* AddBlossomServerView.swift */,
4CE4F9DD2852768D00C00DD9 /* ConfigView.swift */,
);
path = Views;
Expand Down Expand Up @@ -5748,6 +5796,13 @@
4CF0ABEE29844B5500D66079 /* AnyEncodable.swift in Sources */,
B57B4C662B312C3700A232C0 /* NostrAuth.swift in Sources */,
D7DB930B2D69486700DA1EE5 /* NIP65.swift in Sources */,
BL0SS0M0000000000000011 /* BlossomTypes.swift in Sources */,
BL0SS0M0000000000000021 /* BlossomAuth.swift in Sources */,
BL0SS0M0000000000000031 /* BlossomUploader.swift in Sources */,
BL0SS0M0000000000000041 /* BlossomServerList.swift in Sources */,
BL0SS0M0000000000000051 /* BlossomServerListManager.swift in Sources */,
BL0SS0M0000000000000061 /* MediaServerSettingsView.swift in Sources */,
BL0SS0M0000000000000071 /* AddBlossomServerView.swift in Sources */,
4CB8838D296F710400DC99E7 /* Reposted.swift in Sources */,
4CE6DEE927F7A08100C66700 /* ContentView.swift in Sources */,
2710433D2E6BFE340005C3B0 /* PostingTimelineSwitcherView.swift in Sources */,
Expand Down Expand Up @@ -6670,6 +6725,13 @@
82D6FBE12CD99F7900C925F4 /* NotificationSettingsView.swift in Sources */,
82D6FBE22CD99F7900C925F4 /* AppearanceSettingsView.swift in Sources */,
D7DB930A2D69486700DA1EE5 /* NIP65.swift in Sources */,
BL0SS0M0000000000000012 /* BlossomTypes.swift in Sources */,
BL0SS0M0000000000000022 /* BlossomAuth.swift in Sources */,
BL0SS0M0000000000000032 /* BlossomUploader.swift in Sources */,
BL0SS0M0000000000000042 /* BlossomServerList.swift in Sources */,
BL0SS0M0000000000000052 /* BlossomServerListManager.swift in Sources */,
BL0SS0M0000000000000062 /* MediaServerSettingsView.swift in Sources */,
BL0SS0M0000000000000072 /* AddBlossomServerView.swift in Sources */,
82D6FBE32CD99F7900C925F4 /* KeySettingsView.swift in Sources */,
82D6FBE42CD99F7900C925F4 /* ZapSettingsView.swift in Sources */,
82D6FBE52CD99F7900C925F4 /* TranslationSettingsView.swift in Sources */,
Expand Down Expand Up @@ -7152,6 +7214,13 @@
D73E5F022C6A97F4007EB227 /* ZapUserView.swift in Sources */,
D73E5F032C6A97F4007EB227 /* ProfileZapLinkView.swift in Sources */,
D7DB930C2D69486700DA1EE5 /* NIP65.swift in Sources */,
BL0SS0M0000000000000013 /* BlossomTypes.swift in Sources */,
BL0SS0M0000000000000023 /* BlossomAuth.swift in Sources */,
BL0SS0M0000000000000033 /* BlossomUploader.swift in Sources */,
BL0SS0M0000000000000043 /* BlossomServerList.swift in Sources */,
BL0SS0M0000000000000053 /* BlossomServerListManager.swift in Sources */,
BL0SS0M0000000000000063 /* MediaServerSettingsView.swift in Sources */,
BL0SS0M0000000000000073 /* AddBlossomServerView.swift in Sources */,
D73E5F042C6A97F4007EB227 /* AboutView.swift in Sources */,
D73E5F052C6A97F4007EB227 /* ProfileName.swift in Sources */,
D73E5F062C6A97F4007EB227 /* ProfilePictureSelector.swift in Sources */,
Expand Down Expand Up @@ -7509,6 +7578,9 @@
D7CB5D422B116F8900AD4105 /* Contacts.swift in Sources */,
D7CB5D5D2B1176B200AD4105 /* MediaUploader.swift in Sources */,
D7DBD41F2B02F15E002A6197 /* NostrKind.swift in Sources */,
BL0SS0M0000000000000014 /* BlossomTypes.swift in Sources */,
BL0SS0M0000000000000024 /* BlossomAuth.swift in Sources */,
BL0SS0M0000000000000034 /* BlossomUploader.swift in Sources */,
D7CE1B3C2B0BE719002EDAD4 /* TableVerifier.swift in Sources */,
D7EDED2F2B128E8A0018B19C /* CollectionExtension.swift in Sources */,
D7CCFC082B05834500323D86 /* NoteId.swift in Sources */,
Expand Down
139 changes: 139 additions & 0 deletions damus/Core/Blossom/BlossomAuth.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
//
// BlossomAuth.swift
// damus
//
// Created by Claude on 2025-01-15.
//
// Creates kind 24242 authorization events for Blossom servers (BUD-01).
//
// Unlike NIP-98 (kind 27235) which uses URL and method tags,
// Blossom auth uses:
// - ["t", action] (e.g., "upload", "delete", "list")
// - ["x", sha256_hex] (hash of the blob being uploaded/referenced)
// - ["expiration", ts] (unix timestamp when auth expires)
//
// The event is base64-encoded and sent in the Authorization header:
// Authorization: Nostr <base64_encoded_event_json>
//

import Foundation

// MARK: - Blossom Authorization

/// Creates a Blossom upload authorization event (kind 24242).
///
/// The authorization event authenticates the user to the Blossom server
/// and proves they intend to upload a specific blob (identified by SHA256 hash).
///
/// - Parameters:
/// - keypair: The user's nostr keypair for signing
/// - sha256Hex: The SHA256 hash of the blob being uploaded (hex-encoded)
/// - expirationSeconds: How long until the auth expires (default 5 minutes)
/// - Returns: Base64-encoded authorization string for the HTTP header, or nil on failure
///
/// Example usage:
/// ```swift
/// guard let auth = create_blossom_upload_auth(keypair: keypair, sha256Hex: hash) else {
/// return // auth creation failed
/// }
/// request.setValue("Nostr " + auth, forHTTPHeaderField: "Authorization")
/// ```
func create_blossom_upload_auth(keypair: Keypair, sha256Hex: String, expirationSeconds: TimeInterval = 300) -> String? {
return create_blossom_auth(
keypair: keypair,
action: "upload",
sha256Hex: sha256Hex,
expirationSeconds: expirationSeconds,
content: "Upload blob"
)
}

/// Creates a Blossom delete authorization event (kind 24242).
///
/// - Parameters:
/// - keypair: The user's nostr keypair for signing
/// - sha256Hex: The SHA256 hash of the blob to delete (hex-encoded)
/// - expirationSeconds: How long until the auth expires (default 5 minutes)
/// - Returns: Base64-encoded authorization string for the HTTP header, or nil on failure
func create_blossom_delete_auth(keypair: Keypair, sha256Hex: String, expirationSeconds: TimeInterval = 300) -> String? {
return create_blossom_auth(
keypair: keypair,
action: "delete",
sha256Hex: sha256Hex,
expirationSeconds: expirationSeconds,
content: "Delete blob"
)
}

// MARK: - Private Implementation

/// Creates a Blossom authorization event (kind 24242) for any action.
///
/// Per BUD-01, the event must have:
/// - kind: 24242
/// - created_at: in the past (at event creation time)
/// - expiration tag: unix timestamp in the future
/// - t tag: the action verb ("upload", "delete", "list", "get")
/// - x tag(s): sha256 hash(es) of the blob(s) being referenced
/// - content: human-readable description of the action
///
/// - Parameters:
/// - keypair: The user's nostr keypair for signing
/// - action: The action verb ("upload", "delete", "list", "get")
/// - sha256Hex: The SHA256 hash of the blob (hex-encoded), nil for actions that don't need it
/// - expirationSeconds: How long until the auth expires
/// - content: Human-readable description of the action
/// - Returns: Base64-encoded authorization string, or nil on failure
private func create_blossom_auth(
keypair: Keypair,
action: String,
sha256Hex: String?,
expirationSeconds: TimeInterval,
content: String
) -> String? {
let now = UInt32(Date().timeIntervalSince1970)
let expiration = UInt32(Date().timeIntervalSince1970 + expirationSeconds)

// Build tags array
// Required: ["t", action] and ["expiration", timestamp]
// Optional: ["x", sha256] for upload/delete actions
var tags: [[String]] = [
["t", action],
["expiration", String(expiration)]
]

// Add the blob hash if provided (required for upload/delete)
if let hash = sha256Hex {
tags.append(["x", hash])
}

// Create the kind 24242 event
// NdbNote handles signing automatically when given a full keypair
guard let authNote = NdbNote(
content: content,
keypair: keypair,
kind: 24242,
tags: tags,
createdAt: now
) else {
return nil
}

// Encode to JSON and then base64
// This follows the same pattern as NIP-98 auth
guard let jsonData = try? encode_json_data(authNote) else {
return nil
}

return base64_encode(jsonData.bytes)
}

// MARK: - Authorization Header Builder

/// Builds the full Authorization header value for Blossom requests.
///
/// - Parameter base64Auth: The base64-encoded authorization event
/// - Returns: The full header value in format "Nostr <base64>"
func blossom_authorization_header(_ base64Auth: String) -> String {
return "Nostr " + base64Auth
}
Loading