welcome.go raw
1 package marmot
2
3 import (
4 "fmt"
5 "time"
6
7 "next.orly.dev/pkg/nostr/crypto/encryption"
8 "next.orly.dev/pkg/nostr/encoders/event"
9 "next.orly.dev/pkg/nostr/encoders/hex"
10 "next.orly.dev/pkg/nostr/encoders/tag"
11 "next.orly.dev/pkg/nostr/interfaces/signer"
12 "github.com/emersion/go-mls"
13 )
14
15 // WelcomeToGiftWrap creates a kind 1059 gift-wrapped event containing the MLS
16 // Welcome message. The welcome is NIP-44 encrypted to the recipient so only
17 // they can decrypt and join the group.
18 func WelcomeToGiftWrap(welcome *mls.Welcome, recipientPub []byte, sign signer.I) (*event.E, error) {
19 welcomeBytes := welcome.Bytes()
20
21 // NIP-44 encrypt the welcome to the recipient
22 convKey, err := encryption.GenerateConversationKey(sign.Sec(), recipientPub)
23 if err != nil {
24 return nil, fmt.Errorf("generate conversation key: %w", err)
25 }
26 ciphertext, err := encryption.Encrypt(convKey, welcomeBytes, nil)
27 if err != nil {
28 return nil, fmt.Errorf("encrypt welcome: %w", err)
29 }
30
31 ev := event.New()
32 ev.CreatedAt = time.Now().Unix()
33 ev.Kind = KindGiftWrap
34 ev.Content = []byte(ciphertext)
35 ev.Tags = tag.NewS(
36 tag.NewFromAny("p", hex.Enc(recipientPub)),
37 )
38 if err := ev.Sign(sign); err != nil {
39 return nil, fmt.Errorf("sign gift wrap: %w", err)
40 }
41 return ev, nil
42 }
43
44 // UnwrapWelcome decrypts a kind 1059 gift-wrapped event and extracts the MLS
45 // Welcome message.
46 func UnwrapWelcome(ev *event.E, sign signer.I) (*mls.Welcome, error) {
47 if ev.Kind != KindGiftWrap {
48 return nil, fmt.Errorf("expected kind %d, got %d", KindGiftWrap, ev.Kind)
49 }
50
51 // The sender's pubkey is in the event
52 senderPub := ev.Pubkey
53
54 // NIP-44 decrypt using our secret key and the sender's pubkey
55 convKey, err := encryption.GenerateConversationKey(sign.Sec(), senderPub)
56 if err != nil {
57 return nil, fmt.Errorf("generate conversation key: %w", err)
58 }
59 plaintext, err := encryption.Decrypt(convKey, string(ev.Content))
60 if err != nil {
61 return nil, fmt.Errorf("decrypt welcome: %w", err)
62 }
63
64 welcome, err := mls.UnmarshalWelcome([]byte(plaintext))
65 if err != nil {
66 return nil, fmt.Errorf("unmarshal welcome: %w", err)
67 }
68 return welcome, nil
69 }
70