sign_test.go raw

   1  package bridge
   2  
   3  import (
   4  	"testing"
   5  	"time"
   6  
   7  	"next.orly.dev/pkg/nostr/crypto/encryption"
   8  	"next.orly.dev/pkg/nostr/encoders/envelopes/eventenvelope"
   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/nostr/utils"
  14  )
  15  
  16  func TestSignAndVerifyDM(t *testing.T) {
  17  	// Create a signer (like the bridge identity)
  18  	sign, err := p8k.New()
  19  	if err != nil {
  20  		t.Fatalf("create signer: %v", err)
  21  	}
  22  	if err := sign.Generate(); err != nil {
  23  		t.Fatalf("generate key: %v", err)
  24  	}
  25  
  26  	// Create a recipient signer
  27  	recipient, err := p8k.New()
  28  	if err != nil {
  29  		t.Fatalf("create recipient: %v", err)
  30  	}
  31  	if err := recipient.Generate(); err != nil {
  32  		t.Fatalf("generate recipient key: %v", err)
  33  	}
  34  
  35  	recipientPubHex := hex.Enc(recipient.Pub())
  36  	t.Logf("sender pubkey: %s", hex.Enc(sign.Pub()))
  37  	t.Logf("recipient pubkey: %s", recipientPubHex)
  38  
  39  	// Encrypt content like the bridge does
  40  	conversationKey, err := encryption.GenerateConversationKey(
  41  		sign.Sec(), recipient.Pub(),
  42  	)
  43  	if err != nil {
  44  		t.Fatalf("generate conversation key: %v", err)
  45  	}
  46  
  47  	encrypted, err := encryption.Encrypt(conversationKey, []byte("Hello, this is a test DM!"), nil)
  48  	if err != nil {
  49  		t.Fatalf("encrypt: %v", err)
  50  	}
  51  
  52  	// Build event exactly like makeSendDM does
  53  	ev := &event.E{
  54  		Content:   []byte(encrypted),
  55  		CreatedAt: time.Now().Unix(),
  56  		Kind:      4,
  57  		Tags: tag.NewS(
  58  			tag.NewFromAny("p", recipientPubHex),
  59  		),
  60  	}
  61  
  62  	// Sign
  63  	if err := ev.Sign(sign); err != nil {
  64  		t.Fatalf("sign: %v", err)
  65  	}
  66  
  67  	t.Logf("event ID: %0x", ev.ID)
  68  	t.Logf("event pubkey: %0x", ev.Pubkey)
  69  	t.Logf("event sig len: %d", len(ev.Sig))
  70  
  71  	// Verify directly (no round-trip)
  72  	valid, err := ev.Verify()
  73  	if err != nil {
  74  		t.Fatalf("direct verify error: %v", err)
  75  	}
  76  	if !valid {
  77  		t.Fatal("direct verify: signature is invalid!")
  78  	}
  79  	t.Log("direct verify: OK")
  80  
  81  	// Now simulate what happens over WebSocket:
  82  	// 1. Marshal to JSON (like ws.Client.Publish does)
  83  	submission := eventenvelope.NewSubmissionWith(ev)
  84  	jsonBytes := submission.Marshal(nil)
  85  	t.Logf("marshaled JSON (%d bytes): %s", len(jsonBytes), string(jsonBytes[:min(200, len(jsonBytes))]))
  86  
  87  	// 2. Unmarshal from JSON (like the relay does)
  88  	env2 := eventenvelope.NewSubmission()
  89  	_, err = env2.Unmarshal(jsonBytes)
  90  	if err != nil {
  91  		t.Fatalf("unmarshal: %v", err)
  92  	}
  93  
  94  	// 3. Check canonical form matches
  95  	canonical1 := ev.ToCanonical(nil)
  96  	canonical2 := env2.E.ToCanonical(nil)
  97  	t.Logf("canonical1: %s", string(canonical1))
  98  	t.Logf("canonical2: %s", string(canonical2))
  99  
 100  	if !utils.FastEqual(canonical1, canonical2) {
 101  		t.Fatalf("canonical forms differ!\n  original:   %s\n  roundtrip:  %s", string(canonical1), string(canonical2))
 102  	}
 103  	t.Log("canonical forms match")
 104  
 105  	// 4. Validate ID (like ValidateEventID does)
 106  	calculatedID := env2.E.GetIDBytes()
 107  	if !utils.FastEqual(calculatedID, env2.E.ID) {
 108  		t.Fatalf("ID mismatch: event has %0x, computed %0x", env2.E.ID, calculatedID)
 109  	}
 110  	t.Log("ID validation: OK")
 111  
 112  	// 5. Verify signature (like ValidateSignature does)
 113  	valid2, err := env2.E.Verify()
 114  	if err != nil {
 115  		t.Fatalf("roundtrip verify error: %v", err)
 116  	}
 117  	if !valid2 {
 118  		t.Fatal("roundtrip verify: signature is invalid!")
 119  	}
 120  	t.Log("roundtrip verify: OK")
 121  }
 122