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