From 49650e11ebbe3f36bb95f0655d0a56864de24ba6 Mon Sep 17 00:00:00 2001 From: fiatjaf Date: Sun, 15 Sep 2024 11:06:42 -0300 Subject: [PATCH] keyer, nip17, nip44, nip59: this time is different! --- keyer/lib.go | 7 +++--- keyer/plain.go | 11 +++++---- nip17/nip17.go | 17 +++++++++++--- nip44/nip44.go | 23 +++++++++--------- nip59/nip59.go | 64 ++++++++++++++++++++++++-------------------------- 5 files changed, 67 insertions(+), 55 deletions(-) diff --git a/keyer/lib.go b/keyer/lib.go index 6beb706..029222f 100644 --- a/keyer/lib.go +++ b/keyer/lib.go @@ -12,6 +12,7 @@ import ( "github.com/nbd-wtf/go-nostr/nip19" "github.com/nbd-wtf/go-nostr/nip46" "github.com/nbd-wtf/go-nostr/nip49" + "github.com/puzpuzpuz/xsync/v3" ) type Keyer interface { @@ -61,7 +62,7 @@ func New(ctx context.Context, pool *nostr.SimplePool, input string, opts *Signer return nil, fmt.Errorf("failed to decrypt with given password: %w", err) } pk, _ := nostr.GetPublicKey(sec) - return KeySigner{sec, pk, make(map[string][32]byte)}, nil + return KeySigner{sec, pk, xsync.NewMapOf[string, [32]byte]()}, nil } else if nip46.IsValidBunkerURL(input) || nip05.IsValidIdentifier(input) { bcsk := nostr.GeneratePrivateKey() oa := func(url string) { println("auth_url received but not handled") } @@ -81,11 +82,11 @@ func New(ctx context.Context, pool *nostr.SimplePool, input string, opts *Signer } else if prefix, parsed, err := nip19.Decode(input); err == nil && prefix == "nsec" { sec := parsed.(string) pk, _ := nostr.GetPublicKey(sec) - return KeySigner{sec, pk, make(map[string][32]byte)}, nil + return KeySigner{sec, pk, xsync.NewMapOf[string, [32]byte]()}, nil } else if _, err := hex.DecodeString(input); err == nil && len(input) < 64 { input = strings.Repeat("0", 64-len(input)) + input // if the key is like '01', fill all the left zeroes pk, _ := nostr.GetPublicKey(input) - return KeySigner{input, pk, make(map[string][32]byte)}, nil + return KeySigner{input, pk, xsync.NewMapOf[string, [32]byte]()}, nil } return nil, fmt.Errorf("unsupported input '%s'", input) diff --git a/keyer/plain.go b/keyer/plain.go index a58ff18..c50904a 100644 --- a/keyer/plain.go +++ b/keyer/plain.go @@ -5,6 +5,7 @@ import ( "github.com/nbd-wtf/go-nostr" "github.com/nbd-wtf/go-nostr/nip44" + "github.com/puzpuzpuz/xsync/v3" ) // Keysigner is a signer that holds the private key in memory and can do all the operations instantly and easily. @@ -12,34 +13,34 @@ type KeySigner struct { sk string pk string - conversationKeys map[string][32]byte + conversationKeys *xsync.MapOf[string, [32]byte] } func (ks KeySigner) SignEvent(ctx context.Context, evt *nostr.Event) error { return evt.Sign(ks.sk) } func (ks KeySigner) GetPublicKey(ctx context.Context) string { return ks.pk } func (ks KeySigner) Encrypt(ctx context.Context, plaintext string, recipient string) (string, error) { - ck, ok := ks.conversationKeys[recipient] + ck, ok := ks.conversationKeys.Load(recipient) if !ok { var err error ck, err = nip44.GenerateConversationKey(recipient, ks.sk) if err != nil { return "", err } - ks.conversationKeys[recipient] = ck + ks.conversationKeys.Store(recipient, ck) } return nip44.Encrypt(plaintext, ck) } func (ks KeySigner) Decrypt(ctx context.Context, base64ciphertext string, sender string) (string, error) { - ck, ok := ks.conversationKeys[sender] + ck, ok := ks.conversationKeys.Load(sender) if !ok { var err error ck, err = nip44.GenerateConversationKey(sender, ks.sk) if err != nil { return "", err } - ks.conversationKeys[sender] = ck + ks.conversationKeys.Store(sender, ck) } return nip44.Decrypt(base64ciphertext, ck) } diff --git a/nip17/nip17.go b/nip17/nip17.go index 16a1ee5..c17c2e8 100644 --- a/nip17/nip17.go +++ b/nip17/nip17.go @@ -49,9 +49,20 @@ func PrepareMessage( } rumor.ID = rumor.GetID() - wraps, err := nip59.GiftWrap( + toUs, err = nip59.GiftWrap( rumor, - []string{ourPubkey, recipientPubKey}, + ourPubkey, + func(s string) (string, error) { return kr.Encrypt(ctx, s, ourPubkey) }, + func(e *nostr.Event) error { return kr.SignEvent(ctx, e) }, + modify, + ) + if err != nil { + return nostr.Event{}, nostr.Event{}, err + } + + toThem, err = nip59.GiftWrap( + rumor, + ourPubkey, func(s string) (string, error) { return kr.Encrypt(ctx, s, recipientPubKey) }, func(e *nostr.Event) error { return kr.SignEvent(ctx, e) }, modify, @@ -60,7 +71,7 @@ func PrepareMessage( return nostr.Event{}, nostr.Event{}, err } - return wraps[0], wraps[1], nil + return toUs, toThem, nil } // ListenForMessages returns a channel with the rumors already decrypted and checked diff --git a/nip44/nip44.go b/nip44/nip44.go index 23aee29..e01a6e5 100644 --- a/nip44/nip44.go +++ b/nip44/nip44.go @@ -58,7 +58,7 @@ func Encrypt(plaintext string, conversationKey [32]byte, applyOptions ...func(op } } - enc, cc20nonce, auth, err := messageKeys(conversationKey, nonce) + cc20key, cc20nonce, hmackey, err := messageKeys(conversationKey, nonce) if err != nil { return "", err } @@ -74,12 +74,12 @@ func Encrypt(plaintext string, conversationKey [32]byte, applyOptions ...func(op binary.BigEndian.PutUint16(padded, uint16(size)) copy(padded[2:], plain) - ciphertext, err := chacha(enc, cc20nonce, []byte(padded)) + ciphertext, err := chacha(cc20key, cc20nonce, []byte(padded)) if err != nil { return "", err } - mac, err := sha256Hmac(auth, ciphertext, nonce) + mac, err := sha256Hmac(hmackey, ciphertext, nonce) if err != nil { return "", err } @@ -120,12 +120,12 @@ func Decrypt(b64ciphertextWrapped string, conversationKey [32]byte) (string, err copy(nonce[:], decoded[1:33]) ciphertext := decoded[33 : dLen-32] givenMac := decoded[dLen-32:] - enc, cc20nonce, auth, err := messageKeys(conversationKey, nonce) + cc20key, cc20nonce, hmackey, err := messageKeys(conversationKey, nonce) if err != nil { return "", err } - expectedMac, err := sha256Hmac(auth, ciphertext, nonce) + expectedMac, err := sha256Hmac(hmackey, ciphertext, nonce) if err != nil { return "", err } @@ -134,7 +134,7 @@ func Decrypt(b64ciphertextWrapped string, conversationKey [32]byte) (string, err return "", fmt.Errorf("invalid hmac") } - padded, err := chacha(enc, cc20nonce, ciphertext) + padded, err := chacha(cc20key, cc20nonce, ciphertext) if err != nil { return "", err } @@ -191,8 +191,9 @@ func sha256Hmac(key []byte, ciphertext []byte, nonce [32]byte) ([]byte, error) { func messageKeys(conversationKey [32]byte, nonce [32]byte) ([]byte, []byte, []byte, error) { r := hkdf.Expand(sha256.New, conversationKey[:], nonce[:]) - enc := make([]byte, 32) - if _, err := io.ReadFull(r, enc); err != nil { + + cc20key := make([]byte, 32) + if _, err := io.ReadFull(r, cc20key); err != nil { return nil, nil, nil, err } @@ -201,12 +202,12 @@ func messageKeys(conversationKey [32]byte, nonce [32]byte) ([]byte, []byte, []by return nil, nil, nil, err } - auth := make([]byte, 32) - if _, err := io.ReadFull(r, auth); err != nil { + hmacKey := make([]byte, 32) + if _, err := io.ReadFull(r, hmacKey); err != nil { return nil, nil, nil, err } - return enc, cc20nonce, auth, nil + return cc20key, cc20nonce, hmacKey, nil } func calcPadding(sLen int) int { diff --git a/nip59/nip59.go b/nip59/nip59.go index 111dcb4..a0e3ea6 100644 --- a/nip59/nip59.go +++ b/nip59/nip59.go @@ -9,19 +9,20 @@ import ( "github.com/nbd-wtf/go-nostr/nip44" ) -// Seal takes a 'rumor', encrypts it with our own key, making a 'seal', then encrypts that with a nonce key and +// GiftWrap takes a 'rumor', encrypts it with our own key, making a 'seal', then encrypts that with a nonce key and // signs that (after potentially applying a modify function, which can be nil otherwise), yielding a 'gift-wrap'. func GiftWrap( rumor nostr.Event, - recipients []string, // return one giftwrap addressed to each of these + recipient string, encrypt func(plaintext string) (string, error), sign func(*nostr.Event) error, modify func(*nostr.Event), -) ([]nostr.Event, error) { +) (nostr.Event, error) { rumor.Sig = "" + rumorCiphertext, err := encrypt(rumor.String()) if err != nil { - return nil, err + return nostr.Event{}, err } seal := nostr.Event{ @@ -31,39 +32,36 @@ func GiftWrap( Tags: make(nostr.Tags, 0), } if err := sign(&seal); err != nil { - return nil, err + return nostr.Event{}, err + } + + nonceKey := nostr.GeneratePrivateKey() + temporaryConversationKey, err := nip44.GenerateConversationKey(recipient, nonceKey) + if err != nil { + return nostr.Event{}, err + } + + sealCiphertext, err := nip44.Encrypt(seal.String(), temporaryConversationKey) + if err != nil { + return nostr.Event{}, err } - results := make([]nostr.Event, len(recipients)) - for i, recipient := range recipients { - nonceKey := nostr.GeneratePrivateKey() - temporaryConversationKey, err := nip44.GenerateConversationKey(recipient, nonceKey) - if err != nil { - return nil, err - } - sealCiphertext, err := nip44.Encrypt(seal.String(), temporaryConversationKey) - if err != nil { - return nil, err - } - - gw := nostr.Event{ - Kind: 1059, - Content: sealCiphertext, - CreatedAt: nostr.Now() - nostr.Timestamp(60*rand.Int63n(600) /* up to 6 hours in the past */), - Tags: nostr.Tags{ - nostr.Tag{"p", recipient}, - }, - } - if modify != nil { - modify(&gw) - } - if err := gw.Sign(nonceKey); err != nil { - return nil, err - } - results[i] = gw + gw := nostr.Event{ + Kind: 1059, + Content: sealCiphertext, + CreatedAt: nostr.Now() - nostr.Timestamp(60*rand.Int63n(600) /* up to 6 hours in the past */), + Tags: nostr.Tags{ + nostr.Tag{"p", recipient}, + }, + } + if modify != nil { + modify(&gw) + } + if err := gw.Sign(nonceKey); err != nil { + return nostr.Event{}, err } - return results, nil + return gw, nil } func GiftUnwrap(