giftwrap_test.go raw
1 package bridge
2
3 import (
4 "testing"
5
6 "next.orly.dev/pkg/nostr/crypto/keys"
7 "github.com/stretchr/testify/assert"
8 "github.com/stretchr/testify/require"
9 )
10
11 func TestGiftWrap_RoundTrip(t *testing.T) {
12 // Create sender and recipient signers
13 senderSK, err := keys.GenerateSecretKey()
14 require.NoError(t, err)
15 sender, err := keys.SecretBytesToSigner(senderSK)
16 require.NoError(t, err)
17
18 recipientSK, err := keys.GenerateSecretKey()
19 require.NoError(t, err)
20 recipient, err := keys.SecretBytesToSigner(recipientSK)
21 require.NoError(t, err)
22
23 recipientPubHex := hexEnc(recipient.Pub())
24 senderPubHex := hexEnc(sender.Pub())
25
26 // Wrap a message from sender to recipient
27 content := "subscribe testuser"
28 gw, err := wrapGiftWrap(recipientPubHex, content, sender)
29 require.NoError(t, err)
30
31 assert.Equal(t, uint16(1059), gw.Kind)
32 assert.NotEmpty(t, gw.Content)
33 assert.NotEmpty(t, gw.Sig)
34
35 // The gift wrap pubkey should NOT be the sender's real pubkey (ephemeral key)
36 assert.NotEqual(t, sender.Pub(), gw.Pubkey,
37 "gift wrap should use ephemeral key, not sender's real key")
38
39 // Unwrap as recipient
40 dm, err := unwrapGiftWrap(gw, recipient)
41 require.NoError(t, err)
42
43 assert.Equal(t, senderPubHex, dm.SenderPubHex)
44 assert.Equal(t, content, dm.Content)
45 }
46
47 func TestGiftWrap_WrongRecipient(t *testing.T) {
48 senderSK, err := keys.GenerateSecretKey()
49 require.NoError(t, err)
50 sender, err := keys.SecretBytesToSigner(senderSK)
51 require.NoError(t, err)
52
53 recipientSK, err := keys.GenerateSecretKey()
54 require.NoError(t, err)
55 recipient, err := keys.SecretBytesToSigner(recipientSK)
56 require.NoError(t, err)
57
58 // Third party tries to unwrap
59 thirdSK, err := keys.GenerateSecretKey()
60 require.NoError(t, err)
61 third, err := keys.SecretBytesToSigner(thirdSK)
62 require.NoError(t, err)
63
64 recipientPubHex := hexEnc(recipient.Pub())
65
66 gw, err := wrapGiftWrap(recipientPubHex, "secret message", sender)
67 require.NoError(t, err)
68
69 // Third party cannot decrypt
70 _, err = unwrapGiftWrap(gw, third)
71 assert.Error(t, err)
72 }
73
74 func TestGiftWrap_TimestampRandomized(t *testing.T) {
75 senderSK, err := keys.GenerateSecretKey()
76 require.NoError(t, err)
77 sender, err := keys.SecretBytesToSigner(senderSK)
78 require.NoError(t, err)
79
80 recipientSK, err := keys.GenerateSecretKey()
81 require.NoError(t, err)
82 recipient, err := keys.SecretBytesToSigner(recipientSK)
83 require.NoError(t, err)
84
85 recipientPubHex := hexEnc(recipient.Pub())
86
87 // Create multiple gift wraps and verify timestamps differ
88 timestamps := make(map[int64]bool)
89 for i := 0; i < 10; i++ {
90 gw, err := wrapGiftWrap(recipientPubHex, "test", sender)
91 require.NoError(t, err)
92 timestamps[gw.CreatedAt] = true
93 }
94
95 // With random offsets of +/- 2 days, we should get multiple different timestamps
96 assert.Greater(t, len(timestamps), 1,
97 "gift wrap timestamps should be randomized")
98 }
99
100 func TestGiftWrap_LongContent(t *testing.T) {
101 senderSK, err := keys.GenerateSecretKey()
102 require.NoError(t, err)
103 sender, err := keys.SecretBytesToSigner(senderSK)
104 require.NoError(t, err)
105
106 recipientSK, err := keys.GenerateSecretKey()
107 require.NoError(t, err)
108 recipient, err := keys.SecretBytesToSigner(recipientSK)
109 require.NoError(t, err)
110
111 recipientPubHex := hexEnc(recipient.Pub())
112
113 // Test with a long email-like content
114 content := "To: alice@example.com\nSubject: Important\n\n" +
115 "This is a much longer message body that tests whether the " +
116 "gift wrap encryption handles larger payloads correctly. " +
117 "It includes multiple paragraphs and special characters like " +
118 "<html>, &, \"quotes\", and unicode: 日本語テスト"
119
120 gw, err := wrapGiftWrap(recipientPubHex, content, sender)
121 require.NoError(t, err)
122
123 dm, err := unwrapGiftWrap(gw, recipient)
124 require.NoError(t, err)
125
126 assert.Equal(t, content, dm.Content)
127 }
128
129 func TestSenderFormatTracking(t *testing.T) {
130 b := &Bridge{
131 senderFormats: make(map[string]dmFormat),
132 }
133
134 pubA := "aaaa"
135 pubB := "bbbb"
136
137 // Default for unknown sender is kind 4
138 assert.Equal(t, dmFormatKind4, b.getSenderFormat(pubA))
139
140 // Record sender A as gift wrap
141 b.recordSenderFormat(pubA, dmFormatGiftWrap)
142 assert.Equal(t, dmFormatGiftWrap, b.getSenderFormat(pubA))
143
144 // Sender B still defaults to kind 4
145 assert.Equal(t, dmFormatKind4, b.getSenderFormat(pubB))
146
147 // Record sender B as kind 4
148 b.recordSenderFormat(pubB, dmFormatKind4)
149 assert.Equal(t, dmFormatKind4, b.getSenderFormat(pubB))
150
151 // Sender A upgrades to gift wrap doesn't affect B
152 assert.Equal(t, dmFormatGiftWrap, b.getSenderFormat(pubA))
153 }
154
155 // hexEnc is a test helper since we can't import the hex encoder easily.
156 func hexEnc(b []byte) string {
157 const hextable = "0123456789abcdef"
158 dst := make([]byte, len(b)*2)
159 for i, v := range b {
160 dst[i*2] = hextable[v>>4]
161 dst[i*2+1] = hextable[v&0x0f]
162 }
163 return string(dst)
164 }
165