Skip to content

Commit

Permalink
fixes on nip-44 and test vectors.
Browse files Browse the repository at this point in the history
  • Loading branch information
fiatjaf committed Aug 21, 2023
1 parent c5bf589 commit cd86ee2
Show file tree
Hide file tree
Showing 2 changed files with 190 additions and 2 deletions.
11 changes: 9 additions & 2 deletions nip44/nip44.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package nip44

import (
"crypto/rand"
"crypto/sha256"
"encoding/base64"
"encoding/hex"
"fmt"
Expand All @@ -13,7 +14,7 @@ import (
// ComputeSharedSecret returns a shared secret key used to encrypt messages.
// The private and public keys should be hex encoded.
// Uses the Diffie-Hellman key exchange (ECDH) (RFC 4753).
func ComputeSharedSecret(pub string, sk string) (sharedSecret []byte, err error) {
func ComputeSharedSecret(pub string, sk string) ([]byte, error) {
privKeyBytes, err := hex.DecodeString(sk)
if err != nil {
return nil, fmt.Errorf("error decoding sender private key: %w", err)
Expand All @@ -30,7 +31,9 @@ func ComputeSharedSecret(pub string, sk string) (sharedSecret []byte, err error)
return nil, fmt.Errorf("error parsing receiver public key '%s': %w", "02"+pub, err)
}

return btcec.GenerateSharedSecret(privKey, pubKey), nil
sharedSecret := btcec.GenerateSharedSecret(privKey, pubKey)
hash := sha256.Sum256(sharedSecret)
return hash[:], nil
}

// Encrypt encrypts message with key using xchacha20.
Expand All @@ -42,6 +45,10 @@ func Encrypt(message string, key []byte) (string, error) {
return "", fmt.Errorf("error creating nonce: %w", err)
}

return encryptWithNonce(message, key, nonce)
}

func encryptWithNonce(message string, key []byte, nonce []byte) (string, error) {
cipher, err := chacha20.NewUnauthenticatedCipher(key, nonce)
if err != nil {
return "", fmt.Errorf("error creating cipher: %w", err)
Expand Down
181 changes: 181 additions & 0 deletions nip44/nip44_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
package nip44

import (
"encoding/hex"
"encoding/json"
"testing"

"github.com/nbd-wtf/go-nostr"
)

func TestValidSec(t *testing.T) {
vectors := []struct {
Sec1 string `json:"sec1"`
Sec2 string `json:"sec2"`
Shared string `json:"shared"`
Nonce string `json:"nonce"`
Plaintext string `json:"plaintext"`
Ciphertext string `json:"ciphertext"`
Note string `json:"note"`
}{}
if err := json.Unmarshal([]byte(VECTORS_VALID_SEC), &vectors); err != nil {
t.Errorf("failed to read test vectors json")
}

for _, vec := range vectors {
pub1, _ := nostr.GetPublicKey(vec.Sec1)
pub2, _ := nostr.GetPublicKey(vec.Sec2)
ss1, _ := ComputeSharedSecret(pub1, vec.Sec2)
ss2, _ := ComputeSharedSecret(pub2, vec.Sec1)
if hex.EncodeToString(ss1) != hex.EncodeToString(ss2) || hex.EncodeToString(ss1) != vec.Shared {
t.Errorf("invalid shared secret: %x != %x or %x != %s", ss1, ss2, ss1, vec.Shared)
}

nonce, _ := hex.DecodeString(vec.Nonce)
ciphertext, _ := encryptWithNonce(vec.Plaintext, ss1, nonce)
if ciphertext != vec.Ciphertext {
t.Errorf("invalid ciphertext: %s != %s", ciphertext, vec.Ciphertext)
}

plaintext, _ := Decrypt(ciphertext, ss1)
if plaintext != vec.Plaintext {
t.Errorf("invalid plaintext")
}
}
}

func TestValidPub(t *testing.T) {
vectors := []struct {
Sec1 string `json:"sec1"`
Pub2 string `json:"pub2"`
Shared string `json:"shared"`
Nonce string `json:"nonce"`
Plaintext string `json:"plaintext"`
Ciphertext string `json:"ciphertext"`
Note string `json:"note"`
}{}

for _, vec := range vectors {
ss, _ := ComputeSharedSecret(vec.Pub2, vec.Sec1)
if hex.EncodeToString(ss) != vec.Shared {
t.Errorf("invalid shared secret")
}

nonce, _ := hex.DecodeString(vec.Nonce)
ciphertext, _ := encryptWithNonce(vec.Plaintext, ss, nonce)
if ciphertext != vec.Ciphertext {
t.Errorf("invalid ciphertext")
}

plaintext, _ := Decrypt(ciphertext, ss)
if plaintext != vec.Plaintext {
t.Errorf("invalid plaintext")
}
}
}

func TestInvalid(t *testing.T) {
vectors := []struct {
Sec1 string `json:"sec1"`
Pub2 string `json:"pub2"`
Plaintext string `json:"plaintext"`
Note string `json:"note"`
}{}

for _, vec := range vectors {
_, err := ComputeSharedSecret(vec.Pub2, vec.Sec1)
if err == nil {
t.Errorf("should have failed, but didn't: %s", vec.Note)
}
}
}

const (
VECTORS_VALID_SEC = `
[
{
"sec1": "0000000000000000000000000000000000000000000000000000000000000001",
"sec2": "0000000000000000000000000000000000000000000000000000000000000002",
"shared": "0135da2f8acf7b9e3090939432e47684eb888ea38c2173054d4eedffdf152ca5",
"nonce": "121f9d60726777642fd82286791ab4d7461c9502ebcbb6e6",
"plaintext": "a",
"ciphertext": "ARIfnWByZ3dkL9gihnkatNdGHJUC68u25qM=",
"note": "sk1 = 1, sk2 = random, 0x02"
},
{
"sec1": "0000000000000000000000000000000000000000000000000000000000000002",
"sec2": "0000000000000000000000000000000000000000000000000000000000000001",
"shared": "0135da2f8acf7b9e3090939432e47684eb888ea38c2173054d4eedffdf152ca5",
"plaintext": "a",
"ciphertext": "AeCt7jJ8L+WBOTiCSfeXEGXB/C/wgsrSRek=",
"nonce": "e0adee327c2fe58139388249f7971065c1fc2ff082cad245",
"note": "sk1 = 1, sk2 = random, 0x02"
}
]
`
VECTORS_VALID_PUB = `
[
{
"sec1": "fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364139",
"pub2": "0000000000000000000000000000000000000000000000000000000000000002",
"shared": "a6d6a2f7011cdd1aeef325948f48c6efa40f0ec723ae7f5ac7e3889c43481500",
"nonce": "f481750e13dfa90b722b7cce0db39d80b0db2e895cc3001a",
"plaintext": "a",
"ciphertext": "AfSBdQ4T36kLcit8zg2znYCw2y6JXMMAGjM=",
"note": "sec1 = n-2, pub2: random, 0x02"
},
{
"sec1": "0000000000000000000000000000000000000000000000000000000000000002",
"pub2": "1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdeb",
"shared": "4908464f77dd74e11a9b4e4a3bc2467445bd794e8abcbfafb65a6874f9e25a8f",
"nonce": "45c484ba2c0397853183adba6922156e09a2ad4e3e6914f2",
"plaintext": "A Peer-to-Peer Electronic Cash System",
"ciphertext": "AUXEhLosA5eFMYOtumkiFW4Joq1OPmkU8k/25+3+VDFvOU39qkUDl1aiy8Q+0ozTwbhD57VJoIYayYS++hE=",
"note": "sec1 = 2, pub2: "
},
{
"sec1": "0000000000000000000000000000000000000000000000000000000000000001",
"pub2": "79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798",
"shared": "132f39a98c31baaddba6525f5d43f2954472097fa15265f45130bfdb70e51def",
"nonce": "d60de08405cf9bde508147e82224ac6af409c12b9e5492e1",
"plaintext": "A purely peer-to-peer version of electronic cash would allow online payments to be sent directly from one party to another without going through a financial institution. Digital signatures provide part of the solution, but the main benefits are lost if a trusted third party is still required to prevent double-spending.",
"ciphertext": "AdYN4IQFz5veUIFH6CIkrGr0CcErnlSS4VdvoQaP2DCB1dIFL72HSriG1aFABcTlu86hrsG0MdOO9rPdVXc3jptMMzqvIN6tJlHPC8GdwFD5Y8BT76xIIOTJR2W0IdrM7++WC/9harEJAdeWHDAC9zNJX81CpCz4fnV1FZ8GxGLC0nUF7NLeUiNYu5WFXQuO9uWMK0pC7tk3XVogk90X6rwq0MQG9ihT7e1elatDy2YGat+VgQlDrz8ZLRw/lvU+QqeXMQgjqn42sMTrimG6NdKfHJSVWkT6SKZYVsuTyU1Iu5Nk0twEV8d11/MPfsMx4i36arzTC9qxE6jftpOoG8f/jwPTSCEpHdZzrb/CHJcpc+zyOW9BZE2ZOmSxYHAE0ustC9zRNbMT3m6LqxIoHq8j+8Ysu+Cwqr4nUNLYq/Q31UMdDg1oamYS17mWIAS7uf2yF5uT5IlG",
"note": "sec1 == pub2"
}
]
`
VECTORS_INVALID = `
[
{
"sec1": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
"pub2": "1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef",
"plaintext": "a",
"note": "sec1 higher than curve.n"
},
{
"sec1": "0000000000000000000000000000000000000000000000000000000000000000",
"pub2": "1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef",
"plaintext": "a",
"note": "sec1 is 0"
},
{
"sec1": "fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364139",
"pub2": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
"plaintext": "a",
"note": "pub2 is invalid, no sqrt, all-ff"
},
{
"sec1": "fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141",
"pub2": "1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef",
"plaintext": "a",
"note": "sec1 == curve.n"
},
{
"sec1": "0000000000000000000000000000000000000000000000000000000000000002",
"pub2": "1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef",
"plaintext": "a",
"note": "pub2 is invalid, no sqrt"
}
]
`
)

0 comments on commit cd86ee2

Please sign in to comment.