Skip to content

Commit

Permalink
Add support for receiver aliases for gift wrap creation to avoid expo…
Browse files Browse the repository at this point in the history
…sing identities and improve test coverage
  • Loading branch information
tyiu committed Oct 17, 2024
1 parent 0c89ed9 commit 4fb30ba
Show file tree
Hide file tree
Showing 2 changed files with 42 additions and 10 deletions.
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
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

0 comments on commit 4fb30ba

Please sign in to comment.