crypto_test.go raw

   1  package nwc_test
   2  
   3  import (
   4  	"encoding/json"
   5  	"testing"
   6  	"time"
   7  
   8  	"next.orly.dev/pkg/nostr/crypto/encryption"
   9  	"next.orly.dev/pkg/nostr/encoders/event"
  10  	"next.orly.dev/pkg/nostr/encoders/hex"
  11  	"next.orly.dev/pkg/nostr/encoders/tag"
  12  	"next.orly.dev/pkg/nostr/interfaces/signer/p8k"
  13  	"next.orly.dev/pkg/protocol/nwc"
  14  	"next.orly.dev/pkg/utils"
  15  )
  16  
  17  func TestNWCConversationKey(t *testing.T) {
  18  	secret := "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
  19  	walletPubkey := "816fd7f1d000ae81a3da251c91866fc47f4bcd6ce36921e6d46773c32f1d548b"
  20  
  21  	uri := "nostr+walletconnect://" + walletPubkey + "?relay=wss://relay.getalby.com/v1&secret=" + secret
  22  
  23  	parts, err := nwc.ParseConnectionURI(uri)
  24  	if err != nil {
  25  		t.Fatal(err)
  26  	}
  27  
  28  	// Validate conversation key was generated
  29  	convKey := parts.GetConversationKey()
  30  	if len(convKey) == 0 {
  31  		t.Fatal("conversation key should not be empty")
  32  	}
  33  
  34  	// Validate wallet public key
  35  	walletKey := parts.GetWalletPublicKey()
  36  	if len(walletKey) == 0 {
  37  		t.Fatal("wallet public key should not be empty")
  38  	}
  39  
  40  	expected, err := hex.Dec(walletPubkey)
  41  	if err != nil {
  42  		t.Fatal(err)
  43  	}
  44  
  45  	if len(walletKey) != len(expected) {
  46  		t.Fatal("wallet public key length mismatch")
  47  	}
  48  
  49  	for i := range walletKey {
  50  		if walletKey[i] != expected[i] {
  51  			t.Fatal("wallet public key mismatch")
  52  		}
  53  	}
  54  
  55  	// Test passed
  56  }
  57  
  58  func TestNWCEncryptionDecryption(t *testing.T) {
  59  	secret := "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
  60  	walletPubkey := "816fd7f1d000ae81a3da251c91866fc47f4bcd6ce36921e6d46773c32f1d548b"
  61  
  62  	uri := "nostr+walletconnect://" + walletPubkey + "?relay=wss://relay.getalby.com/v1&secret=" + secret
  63  
  64  	parts, err := nwc.ParseConnectionURI(uri)
  65  	if err != nil {
  66  		t.Fatal(err)
  67  	}
  68  
  69  	convKey := parts.GetConversationKey()
  70  	testMessage := `{"method":"get_info","params":null}`
  71  
  72  	// Test encryption
  73  	encrypted, err := encryption.Encrypt(convKey, []byte(testMessage), nil)
  74  	if err != nil {
  75  		t.Fatalf("encryption failed: %v", err)
  76  	}
  77  
  78  	if len(encrypted) == 0 {
  79  		t.Fatal("encrypted message should not be empty")
  80  	}
  81  
  82  	// Test decryption
  83  	decrypted, err := encryption.Decrypt(convKey, encrypted)
  84  	if err != nil {
  85  		t.Fatalf("decryption failed: %v", err)
  86  	}
  87  
  88  	if decrypted != testMessage {
  89  		t.Fatalf(
  90  			"decrypted message mismatch: got %s, want %s", decrypted,
  91  			testMessage,
  92  		)
  93  	}
  94  
  95  	// Test passed
  96  }
  97  
  98  func TestNWCEventCreation(t *testing.T) {
  99  	secretBytes, err := hex.Dec("0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef")
 100  	if err != nil {
 101  		t.Fatal(err)
 102  	}
 103  
 104  	clientKey := p8k.MustNew()
 105  	if err := clientKey.InitSec(secretBytes); err != nil {
 106  		t.Fatal(err)
 107  	}
 108  
 109  	walletPubkey, err := hex.Dec("816fd7f1d000ae81a3da251c91866fc47f4bcd6ce36921e6d46773c32f1d548b")
 110  	if err != nil {
 111  		t.Fatal(err)
 112  	}
 113  
 114  	convKey, err := encryption.GenerateConversationKey(
 115  		clientKey.Sec(), walletPubkey,
 116  	)
 117  	if err != nil {
 118  		t.Fatal(err)
 119  	}
 120  
 121  	request := map[string]any{"method": "get_info"}
 122  	reqBytes, err := json.Marshal(request)
 123  	if err != nil {
 124  		t.Fatal(err)
 125  	}
 126  
 127  	encrypted, err := encryption.Encrypt(convKey, reqBytes, nil)
 128  	if err != nil {
 129  		t.Fatal(err)
 130  	}
 131  
 132  	// Create NWC event
 133  	ev := &event.E{
 134  		Content:   []byte(encrypted),
 135  		CreatedAt: time.Now().Unix(),
 136  		Kind:      23194,
 137  		Tags: tag.NewS(
 138  			tag.NewFromAny("encryption", "nip44_v2"),
 139  			tag.NewFromAny("p", hex.Enc(walletPubkey)),
 140  		),
 141  	}
 142  
 143  	if err := ev.Sign(clientKey); err != nil {
 144  		t.Fatalf("event signing failed: %v", err)
 145  	}
 146  
 147  	// Validate event structure
 148  	if len(ev.Content) == 0 {
 149  		t.Fatal("event content should not be empty")
 150  	}
 151  
 152  	if len(ev.ID) == 0 {
 153  		t.Fatal("event should have ID after signing")
 154  	}
 155  
 156  	if len(ev.Sig) == 0 {
 157  		t.Fatal("event should have signature after signing")
 158  	}
 159  
 160  	// Validate tags
 161  	hasEncryption := false
 162  	hasP := false
 163  	for i := 0; i < ev.Tags.Len(); i++ {
 164  		tag := ev.Tags.GetTagElement(i)
 165  		if tag.Len() >= 2 {
 166  			if utils.FastEqual(
 167  				tag.T[0], "encryption",
 168  			) && utils.FastEqual(tag.T[1], "nip44_v2") {
 169  				hasEncryption = true
 170  			}
 171  			if utils.FastEqual(
 172  				tag.T[0], "p",
 173  			) && utils.FastEqual(tag.T[1], hex.Enc(walletPubkey)) {
 174  				hasP = true
 175  			}
 176  		}
 177  	}
 178  
 179  	if !hasEncryption {
 180  		t.Fatal("event missing encryption tag")
 181  	}
 182  
 183  	if !hasP {
 184  		t.Fatal("event missing p tag")
 185  	}
 186  
 187  	// Test passed
 188  }
 189