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