From f976296e01f22c723f94f364b73b354d193f721a Mon Sep 17 00:00:00 2001 From: fiatjaf Date: Sat, 14 Sep 2024 23:23:53 -0300 Subject: [PATCH] more fixes and tweaks to keyer, 17, 44 and 59. --- keyer/plain.go | 7 +- nip17/nip17.go | 15 ++- nip44/nip44_test.go | 268 ++++++++++++++------------------------------ nip59/nip59.go | 70 ++++++------ 4 files changed, 137 insertions(+), 223 deletions(-) diff --git a/keyer/plain.go b/keyer/plain.go index b056031..a58ff18 100644 --- a/keyer/plain.go +++ b/keyer/plain.go @@ -18,9 +18,10 @@ type KeySigner struct { 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) (c64 string, err error) { +func (ks KeySigner) Encrypt(ctx context.Context, plaintext string, recipient string) (string, error) { ck, ok := ks.conversationKeys[recipient] if !ok { + var err error ck, err = nip44.GenerateConversationKey(recipient, ks.sk) if err != nil { return "", err @@ -30,7 +31,7 @@ func (ks KeySigner) Encrypt(ctx context.Context, plaintext string, recipient str return nip44.Encrypt(plaintext, ck) } -func (ks KeySigner) Decrypt(ctx context.Context, base64ciphertext string, sender string) (plaintext string, err error) { +func (ks KeySigner) Decrypt(ctx context.Context, base64ciphertext string, sender string) (string, error) { ck, ok := ks.conversationKeys[sender] if !ok { var err error @@ -40,5 +41,5 @@ func (ks KeySigner) Decrypt(ctx context.Context, base64ciphertext string, sender } ks.conversationKeys[sender] = ck } - return nip44.Encrypt(plaintext, ck) + return nip44.Decrypt(base64ciphertext, ck) } diff --git a/nip17/nip17.go b/nip17/nip17.go index eae0728..16a1ee5 100644 --- a/nip17/nip17.go +++ b/nip17/nip17.go @@ -37,23 +37,30 @@ func PrepareMessage( kr keyer.Keyer, recipientPubKey string, modify func(*nostr.Event), -) (nostr.Event, error) { +) (toUs nostr.Event, toThem nostr.Event, err error) { + ourPubkey := kr.GetPublicKey(ctx) + rumor := nostr.Event{ Kind: 14, Content: content, Tags: tags, CreatedAt: nostr.Now(), - PubKey: kr.GetPublicKey(ctx), + PubKey: ourPubkey, } rumor.ID = rumor.GetID() - return nip59.GiftWrap( + wraps, err := nip59.GiftWrap( rumor, - recipientPubKey, + []string{ourPubkey, recipientPubKey}, func(s string) (string, error) { return kr.Encrypt(ctx, s, recipientPubKey) }, func(e *nostr.Event) error { return kr.SignEvent(ctx, e) }, modify, ) + if err != nil { + return nostr.Event{}, nostr.Event{}, err + } + + return wraps[0], wraps[1], nil } // ListenForMessages returns a channel with the rumors already decrypted and checked diff --git a/nip44/nip44_test.go b/nip44/nip44_test.go index 3ee01a5..810c2b6 100644 --- a/nip44/nip44_test.go +++ b/nip44/nip44_test.go @@ -5,189 +5,108 @@ import ( "crypto/sha256" "encoding/hex" "fmt" - "hash" "strings" "testing" "github.com/nbd-wtf/go-nostr" - "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func assertCryptPriv(t *testing.T, sk1 string, sk2 string, conversationKey string, salt string, plaintext string, expected string) { - var ( - k1 [32]byte - s []byte - actual string - decrypted string - ok bool - err error - ) - k1, err = hexDecode32Array(conversationKey) - if ok = assert.NoErrorf(t, err, "hex decode failed for conversation key: %v", err); !ok { - return - } - if ok = assertConversationKeyGenerationSec(t, sk1, sk2, conversationKey); !ok { - return - } - s, err = hex.DecodeString(salt) - if ok = assert.NoErrorf(t, err, "hex decode failed for salt: %v", err); !ok { - return - } - actual, err = Encrypt(plaintext, k1, WithCustomNonce(s)) - if ok = assert.NoError(t, err, "encryption failed: %v", err); !ok { - return - } - if ok = assert.Equalf(t, expected, actual, "wrong encryption"); !ok { - return - } - decrypted, err = Decrypt(expected, k1) - if ok = assert.NoErrorf(t, err, "decryption failed: %v", err); !ok { - return - } - assert.Equal(t, decrypted, plaintext, "wrong decryption") + k1, err := hexDecode32Array(conversationKey) + require.NoErrorf(t, err, "hex decode failed for conversation key: %v", err) + assertConversationKeyGenerationSec(t, sk1, sk2, conversationKey) + + customNonce, err := hex.DecodeString(salt) + require.NoErrorf(t, err, "hex decode failed for salt: %v", err) + + actual, err := Encrypt(plaintext, k1, WithCustomNonce(customNonce)) + require.NoError(t, err, "encryption failed: %v", err) + require.Equalf(t, expected, actual, "wrong encryption") + + decrypted, err := Decrypt(expected, k1) + require.NoErrorf(t, err, "decryption failed: %v", err) + + require.Equal(t, decrypted, plaintext, "wrong decryption") } func assertDecryptFail(t *testing.T, conversationKey string, _ string, ciphertext string, msg string) { - var ( - k1 [32]byte - ok bool - err error - ) - k1, err = hexDecode32Array(conversationKey) - if ok = assert.NoErrorf(t, err, "hex decode failed for conversation key: %v", err); !ok { - return - } + k1, err := hexDecode32Array(conversationKey) + require.NoErrorf(t, err, "hex decode failed for conversation key: %v", err) + _, err = Decrypt(ciphertext, k1) - assert.ErrorContains(t, err, msg) + require.ErrorContains(t, err, msg) } func assertConversationKeyFail(t *testing.T, priv string, pub string, msg string) { _, err := GenerateConversationKey(pub, priv) - assert.ErrorContains(t, err, msg) + require.ErrorContains(t, err, msg) } -func assertConversationKeyGeneration(t *testing.T, priv string, pub string, conversationKey string) bool { - var ( - actualConversationKey [32]byte - expectedConversationKey [32]byte - ok bool - err error - ) - expectedConversationKey, err = hexDecode32Array(conversationKey) - if ok = assert.NoErrorf(t, err, "hex decode failed for conversation key: %v", err); !ok { - return false - } - actualConversationKey, err = GenerateConversationKey(pub, priv) - if ok = assert.NoErrorf(t, err, "conversation key generation failed: %v", err); !ok { - return false - } - if ok = assert.Equalf(t, expectedConversationKey, actualConversationKey, "wrong conversation key"); !ok { - return false - } - return true -} +func assertConversationKeyGenerationPub(t *testing.T, priv string, pub string, conversationKey string) { + expectedConversationKey, err := hexDecode32Array(conversationKey) + require.NoErrorf(t, err, "hex decode failed for conversation key: %v", err) -func assertConversationKeyGenerationSec(t *testing.T, sk1 string, sk2 string, conversationKey string) bool { - pub2, err := nostr.GetPublicKey(sk2) - if ok := assert.NoErrorf(t, err, "failed to derive pubkey from sk2: %v", err); !ok { - return false - } - return assertConversationKeyGeneration(t, sk1, pub2, conversationKey) + actualConversationKey, err := GenerateConversationKey(pub, priv) + require.NoErrorf(t, err, "conversation key generation failed: %v", err) + + require.Equalf(t, expectedConversationKey, actualConversationKey, "wrong conversation key") } -func assertConversationKeyGenerationPub(t *testing.T, sk string, pub string, conversationKey string) bool { - return assertConversationKeyGeneration(t, sk, pub, conversationKey) +func assertConversationKeyGenerationSec(t *testing.T, sk1 string, sk2 string, conversationKey string) { + pub2, err := nostr.GetPublicKey(sk2) + require.NoErrorf(t, err, "failed to derive pubkey from sk2: %v", err) + assertConversationKeyGenerationPub(t, sk1, pub2, conversationKey) } func assertMessageKeyGeneration(t *testing.T, conversationKey string, salt string, chachaKey string, chachaSalt string, hmacKey string) bool { - var ( - convKey [32]byte - convSalt []byte - actualChaChaKey []byte - expectedChaChaKey []byte - actualChaChaNonce []byte - expectedChaChaNonce []byte - actualHmacKey []byte - expectedHmacKey []byte - ok bool - err error - ) - convKey, err = hexDecode32Array(conversationKey) - if ok = assert.NoErrorf(t, err, "hex decode failed for convKey: %v", err); !ok { - return false - } - convSalt, err = hex.DecodeString(salt) - if ok = assert.NoErrorf(t, err, "hex decode failed for salt: %v", err); !ok { - return false - } - expectedChaChaKey, err = hex.DecodeString(chachaKey) - if ok = assert.NoErrorf(t, err, "hex decode failed for chacha key: %v", err); !ok { - return false - } - expectedChaChaNonce, err = hex.DecodeString(chachaSalt) - if ok = assert.NoErrorf(t, err, "hex decode failed for chacha nonce: %v", err); !ok { - return false - } - expectedHmacKey, err = hex.DecodeString(hmacKey) - if ok = assert.NoErrorf(t, err, "hex decode failed for hmac key: %v", err); !ok { - return false - } - actualChaChaKey, actualChaChaNonce, actualHmacKey, err = messageKeys(convKey, convSalt) - if ok = assert.NoErrorf(t, err, "message key generation failed: %v", err); !ok { - return false - } - if ok = assert.Equalf(t, expectedChaChaKey, actualChaChaKey, "wrong chacha key"); !ok { - return false - } - if ok = assert.Equalf(t, expectedChaChaNonce, actualChaChaNonce, "wrong chacha nonce"); !ok { - return false - } - if ok = assert.Equalf(t, expectedHmacKey, actualHmacKey, "wrong hmac key"); !ok { - return false - } + convKey, err := hexDecode32Array(conversationKey) + require.NoErrorf(t, err, "hex decode failed for convKey: %v", err) + + convNonce, err := hexDecode32Array(salt) + require.NoErrorf(t, err, "hex decode failed for nonce: %v", err) + + expectedChaChaKey, err := hex.DecodeString(chachaKey) + require.NoErrorf(t, err, "hex decode failed for chacha key: %v", err) + + expectedChaChaNonce, err := hex.DecodeString(chachaSalt) + require.NoErrorf(t, err, "hex decode failed for chacha nonce: %v", err) + + expectedHmacKey, err := hex.DecodeString(hmacKey) + require.NoErrorf(t, err, "hex decode failed for hmac key: %v", err) + + actualChaChaKey, actualChaChaNonce, actualHmacKey, err := messageKeys(convKey, convNonce) + require.NoErrorf(t, err, "message key generation failed: %v", err) + + require.Equalf(t, expectedChaChaKey, actualChaChaKey, "wrong chacha key") + require.Equalf(t, expectedChaChaNonce, actualChaChaNonce, "wrong chacha nonce") + require.Equalf(t, expectedHmacKey, actualHmacKey, "wrong hmac key") return true } func assertCryptLong(t *testing.T, conversationKey string, salt string, pattern string, repeat int, plaintextSha256 string, payloadSha256 string) { - var ( - convKey [32]byte - convSalt []byte - plaintext string - actualPlaintextSha256 string - actualPayload string - actualPayloadSha256 string - h hash.Hash - ok bool - err error - ) - convKey, err = hexDecode32Array(conversationKey) - if ok = assert.NoErrorf(t, err, "hex decode failed for convKey: %v", err); !ok { - return - } - convSalt, err = hex.DecodeString(salt) - if ok = assert.NoErrorf(t, err, "hex decode failed for salt: %v", err); !ok { - return - } - plaintext = "" + convKey, err := hexDecode32Array(conversationKey) + require.NoErrorf(t, err, "hex decode failed for convKey: %v", err) + + customNonce, err := hex.DecodeString(salt) + require.NoErrorf(t, err, "hex decode failed for salt: %v", err) + + plaintext := "" for i := 0; i < repeat; i++ { plaintext += pattern } - h = sha256.New() + h := sha256.New() h.Write([]byte(plaintext)) - actualPlaintextSha256 = hex.EncodeToString(h.Sum(nil)) - if ok = assert.Equalf(t, plaintextSha256, actualPlaintextSha256, "invalid plaintext sha256 hash: %v", err); !ok { - return - } - actualPayload, err = Encrypt(plaintext, convKey, WithCustomNonce(convSalt)) - if ok = assert.NoErrorf(t, err, "encryption failed: %v", err); !ok { - return - } + actualPlaintextSha256 := hex.EncodeToString(h.Sum(nil)) + require.Equalf(t, plaintextSha256, actualPlaintextSha256, "invalid plaintext sha256 hash: %v", err) + + actualPayload, err := Encrypt(plaintext, convKey, WithCustomNonce(customNonce)) + require.NoErrorf(t, err, "encryption failed: %v", err) + h.Reset() h.Write([]byte(actualPayload)) - actualPayloadSha256 = hex.EncodeToString(h.Sum(nil)) - if ok = assert.Equalf(t, payloadSha256, actualPayloadSha256, "invalid payload sha256 hash: %v", err); !ok { - return - } + actualPayloadSha256 := hex.EncodeToString(h.Sum(nil)) + require.Equalf(t, payloadSha256, actualPayloadSha256, "invalid payload sha256 hash: %v", err) } func TestCryptPriv001(t *testing.T) { @@ -1162,38 +1081,23 @@ func TestMaxLength(t *testing.T) { ) } -func assertCryptPub(t *testing.T, sk1 string, pub2 string, conversationKey string, salt string, plaintext string, expected string) { - var ( - k1 [32]byte - s []byte - actual string - decrypted string - ok bool - err error - ) - k1, err = hexDecode32Array(conversationKey) - if ok = assert.NoErrorf(t, err, "hex decode failed for conversation key: %v", err); !ok { - return - } - if ok = assertConversationKeyGenerationPub(t, sk1, pub2, conversationKey); !ok { - return - } - s, err = hex.DecodeString(salt) - if ok = assert.NoErrorf(t, err, "hex decode failed for salt: %v", err); !ok { - return - } - actual, err = Encrypt(plaintext, k1, WithCustomNonce(s)) - if ok = assert.NoError(t, err, "encryption failed: %v", err); !ok { - return - } - if ok = assert.Equalf(t, expected, actual, "wrong encryption"); !ok { - return - } - decrypted, err = Decrypt(expected, k1) - if ok = assert.NoErrorf(t, err, "decryption failed: %v", err); !ok { - return - } - assert.Equal(t, decrypted, plaintext, "wrong decryption") +func assertCryptPub(t *testing.T, sk1 string, pub2 string, conversationKey string, customNonce string, plaintext string, expected string) { + k1, err := hexDecode32Array(conversationKey) + require.NoErrorf(t, err, "hex decode failed for conversation key: %v", err) + + assertConversationKeyGenerationPub(t, sk1, pub2, conversationKey) + + s, err := hex.DecodeString(customNonce) + require.NoErrorf(t, err, "hex decode failed for salt: %v", err) + + actual, err := Encrypt(plaintext, k1, WithCustomNonce(s)) + require.NoError(t, err, "encryption failed: %v", err) + require.Equalf(t, expected, actual, "wrong encryption") + + decrypted, err := Decrypt(expected, k1) + require.NoErrorf(t, err, "decryption failed: %v", err) + + require.Equal(t, decrypted, plaintext, "wrong decryption") } func hexDecode32Array(hexString string) (res [32]byte, err error) { diff --git a/nip59/nip59.go b/nip59/nip59.go index 2fea757..111dcb4 100644 --- a/nip59/nip59.go +++ b/nip59/nip59.go @@ -13,15 +13,15 @@ import ( // signs that (after potentially applying a modify function, which can be nil otherwise), yielding a 'gift-wrap'. func GiftWrap( rumor nostr.Event, - recipientPublicKey string, + recipients []string, // return one giftwrap addressed to each of these 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 nostr.Event{}, err + return nil, err } seal := nostr.Event{ @@ -31,37 +31,39 @@ func GiftWrap( Tags: make(nostr.Tags, 0), } if err := sign(&seal); err != nil { - return nostr.Event{}, err + return nil, err } - nonceKey := nostr.GeneratePrivateKey() - temporaryConversationKey, err := nip44.GenerateConversationKey(recipientPublicKey, nonceKey) - if err != nil { - return nostr.Event{}, err - } - sealCiphertext, err := nip44.Encrypt(seal.String(), temporaryConversationKey) - if err != nil { - return nostr.Event{}, 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", recipientPublicKey}, - }, - } - - if modify != nil { - modify(&gw) - } - - if err := gw.Sign(nonceKey); err != nil { - return gw, 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 } - return gw, nil + return results, nil } func GiftUnwrap( @@ -70,13 +72,13 @@ func GiftUnwrap( ) (rumor nostr.Event, err error) { jseal, err := decrypt(gw.PubKey, gw.Content) if err != nil { - return rumor, err + return rumor, fmt.Errorf("failed to decrypt seal: %w", err) } var seal nostr.Event err = easyjson.Unmarshal([]byte(jseal), &seal) if err != nil { - return rumor, err + return rumor, fmt.Errorf("seal is invalid json: %w", err) } if ok, _ := seal.CheckSignature(); !ok { @@ -85,12 +87,12 @@ func GiftUnwrap( jrumor, err := decrypt(seal.PubKey, seal.Content) if err != nil { - return rumor, err + return rumor, fmt.Errorf("failed to decrypt rumor: %w", err) } err = easyjson.Unmarshal([]byte(jrumor), &rumor) if err != nil { - return rumor, err + return rumor, fmt.Errorf("rumor is invalid json: %w", err) } rumor.PubKey = seal.PubKey