more fixes and tweaks to keyer, 17, 44 and 59.

This commit is contained in:
fiatjaf
2024-09-14 23:23:53 -03:00
parent 46a0c95b96
commit f976296e01
4 changed files with 137 additions and 223 deletions

View File

@@ -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) 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) 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] ck, ok := ks.conversationKeys[recipient]
if !ok { if !ok {
var err error
ck, err = nip44.GenerateConversationKey(recipient, ks.sk) ck, err = nip44.GenerateConversationKey(recipient, ks.sk)
if err != nil { if err != nil {
return "", err return "", err
@@ -30,7 +31,7 @@ func (ks KeySigner) Encrypt(ctx context.Context, plaintext string, recipient str
return nip44.Encrypt(plaintext, ck) 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] ck, ok := ks.conversationKeys[sender]
if !ok { if !ok {
var err error var err error
@@ -40,5 +41,5 @@ func (ks KeySigner) Decrypt(ctx context.Context, base64ciphertext string, sender
} }
ks.conversationKeys[sender] = ck ks.conversationKeys[sender] = ck
} }
return nip44.Encrypt(plaintext, ck) return nip44.Decrypt(base64ciphertext, ck)
} }

View File

@@ -37,23 +37,30 @@ func PrepareMessage(
kr keyer.Keyer, kr keyer.Keyer,
recipientPubKey string, recipientPubKey string,
modify func(*nostr.Event), modify func(*nostr.Event),
) (nostr.Event, error) { ) (toUs nostr.Event, toThem nostr.Event, err error) {
ourPubkey := kr.GetPublicKey(ctx)
rumor := nostr.Event{ rumor := nostr.Event{
Kind: 14, Kind: 14,
Content: content, Content: content,
Tags: tags, Tags: tags,
CreatedAt: nostr.Now(), CreatedAt: nostr.Now(),
PubKey: kr.GetPublicKey(ctx), PubKey: ourPubkey,
} }
rumor.ID = rumor.GetID() rumor.ID = rumor.GetID()
return nip59.GiftWrap( wraps, err := nip59.GiftWrap(
rumor, rumor,
recipientPubKey, []string{ourPubkey, recipientPubKey},
func(s string) (string, error) { return kr.Encrypt(ctx, s, recipientPubKey) }, func(s string) (string, error) { return kr.Encrypt(ctx, s, recipientPubKey) },
func(e *nostr.Event) error { return kr.SignEvent(ctx, e) }, func(e *nostr.Event) error { return kr.SignEvent(ctx, e) },
modify, 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 // ListenForMessages returns a channel with the rumors already decrypted and checked

View File

@@ -5,189 +5,108 @@ import (
"crypto/sha256" "crypto/sha256"
"encoding/hex" "encoding/hex"
"fmt" "fmt"
"hash"
"strings" "strings"
"testing" "testing"
"github.com/nbd-wtf/go-nostr" "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) { func assertCryptPriv(t *testing.T, sk1 string, sk2 string, conversationKey string, salt string, plaintext string, expected string) {
var ( k1, err := hexDecode32Array(conversationKey)
k1 [32]byte require.NoErrorf(t, err, "hex decode failed for conversation key: %v", err)
s []byte assertConversationKeyGenerationSec(t, sk1, sk2, conversationKey)
actual string
decrypted string customNonce, err := hex.DecodeString(salt)
ok bool require.NoErrorf(t, err, "hex decode failed for salt: %v", err)
err error
) actual, err := Encrypt(plaintext, k1, WithCustomNonce(customNonce))
k1, err = hexDecode32Array(conversationKey) require.NoError(t, err, "encryption failed: %v", err)
if ok = assert.NoErrorf(t, err, "hex decode failed for conversation key: %v", err); !ok { require.Equalf(t, expected, actual, "wrong encryption")
return
} decrypted, err := Decrypt(expected, k1)
if ok = assertConversationKeyGenerationSec(t, sk1, sk2, conversationKey); !ok { require.NoErrorf(t, err, "decryption failed: %v", err)
return
} require.Equal(t, decrypted, plaintext, "wrong decryption")
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 assertDecryptFail(t *testing.T, conversationKey string, _ string, ciphertext string, msg string) { func assertDecryptFail(t *testing.T, conversationKey string, _ string, ciphertext string, msg string) {
var ( k1, err := hexDecode32Array(conversationKey)
k1 [32]byte require.NoErrorf(t, err, "hex decode failed for conversation key: %v", err)
ok bool
err error
)
k1, err = hexDecode32Array(conversationKey)
if ok = assert.NoErrorf(t, err, "hex decode failed for conversation key: %v", err); !ok {
return
}
_, err = Decrypt(ciphertext, k1) _, 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) { func assertConversationKeyFail(t *testing.T, priv string, pub string, msg string) {
_, err := GenerateConversationKey(pub, priv) _, 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 { func assertConversationKeyGenerationPub(t *testing.T, priv string, pub string, conversationKey string) {
var ( expectedConversationKey, err := hexDecode32Array(conversationKey)
actualConversationKey [32]byte require.NoErrorf(t, err, "hex decode failed for conversation key: %v", err)
expectedConversationKey [32]byte
ok bool actualConversationKey, err := GenerateConversationKey(pub, priv)
err error require.NoErrorf(t, err, "conversation key generation failed: %v", err)
)
expectedConversationKey, err = hexDecode32Array(conversationKey) require.Equalf(t, expectedConversationKey, actualConversationKey, "wrong conversation key")
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 assertConversationKeyGenerationSec(t *testing.T, sk1 string, sk2 string, conversationKey string) bool { func assertConversationKeyGenerationSec(t *testing.T, sk1 string, sk2 string, conversationKey string) {
pub2, err := nostr.GetPublicKey(sk2) pub2, err := nostr.GetPublicKey(sk2)
if ok := assert.NoErrorf(t, err, "failed to derive pubkey from sk2: %v", err); !ok { require.NoErrorf(t, err, "failed to derive pubkey from sk2: %v", err)
return false assertConversationKeyGenerationPub(t, sk1, pub2, conversationKey)
}
return assertConversationKeyGeneration(t, sk1, pub2, conversationKey)
}
func assertConversationKeyGenerationPub(t *testing.T, sk string, pub string, conversationKey string) bool {
return assertConversationKeyGeneration(t, sk, pub, conversationKey)
} }
func assertMessageKeyGeneration(t *testing.T, conversationKey string, salt string, chachaKey string, chachaSalt string, hmacKey string) bool { func assertMessageKeyGeneration(t *testing.T, conversationKey string, salt string, chachaKey string, chachaSalt string, hmacKey string) bool {
var ( convKey, err := hexDecode32Array(conversationKey)
convKey [32]byte require.NoErrorf(t, err, "hex decode failed for convKey: %v", err)
convSalt []byte
actualChaChaKey []byte convNonce, err := hexDecode32Array(salt)
expectedChaChaKey []byte require.NoErrorf(t, err, "hex decode failed for nonce: %v", err)
actualChaChaNonce []byte
expectedChaChaNonce []byte expectedChaChaKey, err := hex.DecodeString(chachaKey)
actualHmacKey []byte require.NoErrorf(t, err, "hex decode failed for chacha key: %v", err)
expectedHmacKey []byte
ok bool expectedChaChaNonce, err := hex.DecodeString(chachaSalt)
err error require.NoErrorf(t, err, "hex decode failed for chacha nonce: %v", err)
)
convKey, err = hexDecode32Array(conversationKey) expectedHmacKey, err := hex.DecodeString(hmacKey)
if ok = assert.NoErrorf(t, err, "hex decode failed for convKey: %v", err); !ok { require.NoErrorf(t, err, "hex decode failed for hmac key: %v", err)
return false
} actualChaChaKey, actualChaChaNonce, actualHmacKey, err := messageKeys(convKey, convNonce)
convSalt, err = hex.DecodeString(salt) require.NoErrorf(t, err, "message key generation failed: %v", err)
if ok = assert.NoErrorf(t, err, "hex decode failed for salt: %v", err); !ok {
return false require.Equalf(t, expectedChaChaKey, actualChaChaKey, "wrong chacha key")
} require.Equalf(t, expectedChaChaNonce, actualChaChaNonce, "wrong chacha nonce")
expectedChaChaKey, err = hex.DecodeString(chachaKey) require.Equalf(t, expectedHmacKey, actualHmacKey, "wrong hmac key")
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
}
return true return true
} }
func assertCryptLong(t *testing.T, conversationKey string, salt string, pattern string, repeat int, plaintextSha256 string, payloadSha256 string) { func assertCryptLong(t *testing.T, conversationKey string, salt string, pattern string, repeat int, plaintextSha256 string, payloadSha256 string) {
var ( convKey, err := hexDecode32Array(conversationKey)
convKey [32]byte require.NoErrorf(t, err, "hex decode failed for convKey: %v", err)
convSalt []byte
plaintext string customNonce, err := hex.DecodeString(salt)
actualPlaintextSha256 string require.NoErrorf(t, err, "hex decode failed for salt: %v", err)
actualPayload string
actualPayloadSha256 string plaintext := ""
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 = ""
for i := 0; i < repeat; i++ { for i := 0; i < repeat; i++ {
plaintext += pattern plaintext += pattern
} }
h = sha256.New() h := sha256.New()
h.Write([]byte(plaintext)) h.Write([]byte(plaintext))
actualPlaintextSha256 = hex.EncodeToString(h.Sum(nil)) actualPlaintextSha256 := hex.EncodeToString(h.Sum(nil))
if ok = assert.Equalf(t, plaintextSha256, actualPlaintextSha256, "invalid plaintext sha256 hash: %v", err); !ok { require.Equalf(t, plaintextSha256, actualPlaintextSha256, "invalid plaintext sha256 hash: %v", err)
return
} actualPayload, err := Encrypt(plaintext, convKey, WithCustomNonce(customNonce))
actualPayload, err = Encrypt(plaintext, convKey, WithCustomNonce(convSalt)) require.NoErrorf(t, err, "encryption failed: %v", err)
if ok = assert.NoErrorf(t, err, "encryption failed: %v", err); !ok {
return
}
h.Reset() h.Reset()
h.Write([]byte(actualPayload)) h.Write([]byte(actualPayload))
actualPayloadSha256 = hex.EncodeToString(h.Sum(nil)) actualPayloadSha256 := hex.EncodeToString(h.Sum(nil))
if ok = assert.Equalf(t, payloadSha256, actualPayloadSha256, "invalid payload sha256 hash: %v", err); !ok { require.Equalf(t, payloadSha256, actualPayloadSha256, "invalid payload sha256 hash: %v", err)
return
}
} }
func TestCryptPriv001(t *testing.T) { 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) { func assertCryptPub(t *testing.T, sk1 string, pub2 string, conversationKey string, customNonce string, plaintext string, expected string) {
var ( k1, err := hexDecode32Array(conversationKey)
k1 [32]byte require.NoErrorf(t, err, "hex decode failed for conversation key: %v", err)
s []byte
actual string assertConversationKeyGenerationPub(t, sk1, pub2, conversationKey)
decrypted string
ok bool s, err := hex.DecodeString(customNonce)
err error require.NoErrorf(t, err, "hex decode failed for salt: %v", err)
)
k1, err = hexDecode32Array(conversationKey) actual, err := Encrypt(plaintext, k1, WithCustomNonce(s))
if ok = assert.NoErrorf(t, err, "hex decode failed for conversation key: %v", err); !ok { require.NoError(t, err, "encryption failed: %v", err)
return require.Equalf(t, expected, actual, "wrong encryption")
}
if ok = assertConversationKeyGenerationPub(t, sk1, pub2, conversationKey); !ok { decrypted, err := Decrypt(expected, k1)
return require.NoErrorf(t, err, "decryption failed: %v", err)
}
s, err = hex.DecodeString(salt) require.Equal(t, decrypted, plaintext, "wrong decryption")
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 hexDecode32Array(hexString string) (res [32]byte, err error) { func hexDecode32Array(hexString string) (res [32]byte, err error) {

View File

@@ -13,15 +13,15 @@ import (
// signs that (after potentially applying a modify function, which can be nil otherwise), yielding a 'gift-wrap'. // signs that (after potentially applying a modify function, which can be nil otherwise), yielding a 'gift-wrap'.
func GiftWrap( func GiftWrap(
rumor nostr.Event, rumor nostr.Event,
recipientPublicKey string, recipients []string, // return one giftwrap addressed to each of these
encrypt func(plaintext string) (string, error), encrypt func(plaintext string) (string, error),
sign func(*nostr.Event) error, sign func(*nostr.Event) error,
modify func(*nostr.Event), modify func(*nostr.Event),
) (nostr.Event, error) { ) ([]nostr.Event, error) {
rumor.Sig = "" rumor.Sig = ""
rumorCiphertext, err := encrypt(rumor.String()) rumorCiphertext, err := encrypt(rumor.String())
if err != nil { if err != nil {
return nostr.Event{}, err return nil, err
} }
seal := nostr.Event{ seal := nostr.Event{
@@ -31,37 +31,39 @@ func GiftWrap(
Tags: make(nostr.Tags, 0), Tags: make(nostr.Tags, 0),
} }
if err := sign(&seal); err != nil { if err := sign(&seal); err != nil {
return nostr.Event{}, err return nil, err
} }
nonceKey := nostr.GeneratePrivateKey() results := make([]nostr.Event, len(recipients))
temporaryConversationKey, err := nip44.GenerateConversationKey(recipientPublicKey, nonceKey) for i, recipient := range recipients {
if err != nil { nonceKey := nostr.GeneratePrivateKey()
return nostr.Event{}, err temporaryConversationKey, err := nip44.GenerateConversationKey(recipient, nonceKey)
} if err != nil {
sealCiphertext, err := nip44.Encrypt(seal.String(), temporaryConversationKey) return nil, err
if err != nil { }
return nostr.Event{}, 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{ return results, nil
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
}
return gw, nil
} }
func GiftUnwrap( func GiftUnwrap(
@@ -70,13 +72,13 @@ func GiftUnwrap(
) (rumor nostr.Event, err error) { ) (rumor nostr.Event, err error) {
jseal, err := decrypt(gw.PubKey, gw.Content) jseal, err := decrypt(gw.PubKey, gw.Content)
if err != nil { if err != nil {
return rumor, err return rumor, fmt.Errorf("failed to decrypt seal: %w", err)
} }
var seal nostr.Event var seal nostr.Event
err = easyjson.Unmarshal([]byte(jseal), &seal) err = easyjson.Unmarshal([]byte(jseal), &seal)
if err != nil { if err != nil {
return rumor, err return rumor, fmt.Errorf("seal is invalid json: %w", err)
} }
if ok, _ := seal.CheckSignature(); !ok { if ok, _ := seal.CheckSignature(); !ok {
@@ -85,12 +87,12 @@ func GiftUnwrap(
jrumor, err := decrypt(seal.PubKey, seal.Content) jrumor, err := decrypt(seal.PubKey, seal.Content)
if err != nil { if err != nil {
return rumor, err return rumor, fmt.Errorf("failed to decrypt rumor: %w", err)
} }
err = easyjson.Unmarshal([]byte(jrumor), &rumor) err = easyjson.Unmarshal([]byte(jrumor), &rumor)
if err != nil { if err != nil {
return rumor, err return rumor, fmt.Errorf("rumor is invalid json: %w", err)
} }
rumor.PubKey = seal.PubKey rumor.PubKey = seal.PubKey