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>, &amp;, \"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