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

Add support for receiver aliases for gift wrap creation to avoid exposing identities and improve test coverage #184

Merged
merged 1 commit into from
Oct 17, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
20 changes: 11 additions & 9 deletions Sources/NostrSDK/Events/GiftWrap/GiftWrapEvent.swift
Original file line number Diff line number Diff line change
Expand Up @@ -87,36 +87,38 @@ public extension EventCreating {
/// Creates a ``GiftWrapEvent`` that takes a rumor, an unsigned ``NostrEvent``, and seals it in a signed ``SealEvent``, and then wraps that seal encrypted in the content of the gift wrap.
///
/// - Parameters:
/// - withRumor: a ``NostrEvent`` that is not signed.
/// - toRecipient: the ``PublicKey`` of the receiver of the event. This pubkey will automatically be added as a tag to the ``GiftWrapEvent``.
/// - rumor: a ``NostrEvent`` that is not signed.
/// - recipient: the ``PublicKey`` 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 ``PublicKey`` 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 to add to the ``GiftWrapEvent`` 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 ``Keypair`` to sign the seal with. Note that a different random one-time use key is used to sign the gift wrap.
func giftWrap(
withRumor rumor: NostrEvent,
toRecipient recipient: PublicKey,
recipientAlias: PublicKey? = nil,
tags: [Tag] = [],
createdAt: Int64 = Int64(Date.now.timeIntervalSince1970 - TimeInterval.random(in: 0...172800)),
signedBy keypair: Keypair
) throws -> GiftWrapEvent {
let seal = try seal(withRumor: rumor, toRecipient: recipient, signedBy: keypair)
return try giftWrap(withSeal: seal, toRecipient: recipient, tags: tags, createdAt: createdAt, signedBy: keypair)
return try giftWrap(withSeal: seal, toRecipient: recipient, recipientAlias: recipientAlias, tags: tags, createdAt: createdAt)
}

/// Creates a ``GiftWrapEvent`` that takes a signed ``SealEvent``, and then wraps that seal encrypted in the content of the gift wrap.
///
/// - Parameters:
/// - withSeal: a signed ``SealEvent``.
/// - toRecipient: the ``PublicKey`` of the receiver of the event.
/// - seal: a signed ``SealEvent``.
/// - recipient: the ``PublicKey`` 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 ``PublicKey`` 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.
/// - keypair: The real ``Keypair`` to sign the seal with. Note that a different random one-time use key is used to sign the gift wrap.
func giftWrap(
withSeal seal: SealEvent,
toRecipient recipient: PublicKey,
recipientAlias: PublicKey? = nil,
tags: [Tag] = [],
createdAt: Int64 = Int64(Date.now.timeIntervalSince1970 - TimeInterval.random(in: 0...172800)),
signedBy keypair: Keypair
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This parameter was not being used and does not make sense in the context of this function because the seal event already exists -- there's nothing to sign with it and the gift wrap is signed with a one-time-use random keypair.

createdAt: Int64 = Int64(Date.now.timeIntervalSince1970 - TimeInterval.random(in: 0...172800))
) throws -> GiftWrapEvent {
let jsonData = try JSONEncoder().encode(seal)
guard let stringifiedJSON = String(data: jsonData, encoding: .utf8) else {
Expand All @@ -127,7 +129,7 @@ public extension EventCreating {
throw GiftWrapError.keypairGenerationFailed
}

let combinedTags = [Tag(name: .pubkey, value: recipient.hex)] + tags
let combinedTags = [Tag(name: .pubkey, value: (recipientAlias ?? recipient).hex)] + tags

let encryptedSeal = try encrypt(plaintext: stringifiedJSON, privateKeyA: randomKeypair.privateKey, publicKeyB: recipient)
return try GiftWrapEvent(content: encryptedSeal, tags: combinedTags, createdAt: createdAt, signedBy: randomKeypair)
Expand Down
32 changes: 31 additions & 1 deletion Tests/NostrSDKTests/Events/GiftWrap/GiftWrapEventTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,39 @@ final class GiftWrapEventTests: XCTestCase, EventCreating, EventVerifying, Fixtu
func testCreateGiftWrapSucceeds() throws {
let rumor: NostrEvent = try decodeFixture(filename: "rumor")

let giftWrapEvent = try giftWrap(withRumor: rumor, toRecipient: GiftWrapEventTests.recipient.publicKey, signedBy: .test)
let tags = [
Tag(name: "randomTag1", value: "value"),
Tag(name: "randomTag2", value: "value")
]

let giftWrapEvent = try giftWrap(withRumor: rumor, toRecipient: GiftWrapEventTests.recipient.publicKey, tags: tags, signedBy: .test)
try verifyEvent(giftWrapEvent)

XCTAssertNotEqual(giftWrapEvent.pubkey, Keypair.test.publicKey.hex)
XCTAssertEqual(giftWrapEvent.firstValueForTagName(.pubkey), GiftWrapEventTests.recipient.publicKey.hex)
XCTAssertEqual(giftWrapEvent.tags, [Tag.pubkey(GiftWrapEventTests.recipient.publicKey.hex)] + tags)

let unsealedRumor = try giftWrapEvent.unsealedRumor(using: GiftWrapEventTests.recipient.privateKey)

XCTAssertEqual(rumor, unsealedRumor)
}

func testCreateGiftWrapWithRecipientAliasSucceeds() throws {
let rumor: NostrEvent = try decodeFixture(filename: "rumor")
let recipientAlias = try XCTUnwrap(Keypair()?.publicKey)

let tags = [
Tag(name: "randomTag1", value: "value"),
Tag(name: "randomTag2", value: "value")
]

let giftWrapEvent = try giftWrap(withRumor: rumor, toRecipient: GiftWrapEventTests.recipient.publicKey, recipientAlias: recipientAlias, tags: tags, signedBy: .test)
try verifyEvent(giftWrapEvent)

XCTAssertNotEqual(giftWrapEvent.pubkey, Keypair.test.publicKey.hex)
XCTAssertEqual(giftWrapEvent.firstValueForTagName(.pubkey), recipientAlias.hex)
XCTAssertEqual(giftWrapEvent.tags, [Tag.pubkey(recipientAlias.hex)] + tags)

let unsealedRumor = try giftWrapEvent.unsealedRumor(using: GiftWrapEventTests.recipient.privateKey)

XCTAssertEqual(rumor, unsealedRumor)
Expand Down