Make sure privkey is on curve before using unsafe secp256k1.PrivKeyFromBytes

This commit is contained in:
ekzyis 2024-04-24 22:35:27 +02:00
parent ddde532a9e
commit 1defc0bdf6
3 changed files with 53 additions and 74 deletions

View File

@ -1,31 +1,5 @@
**NIP-44 implementation in Go** **NIP-44 implementation in Go**
---
**DISCLAIMER - READ BEFORE USING**
**This library does not make sure yet that the secp256k1 keys you want to use for the conversation key are valid, protected against twist attacks and not contain any other weaknesses as mentioned in the [NIP-44 security audit](https://cure53.de/audit-report_nip44-implementations.pdf).**
**If you really want to use this library before this is fixed, you need to make sure that the keys you use with `GenerateConversationKey` are not affected yourself.**
See [documentation of `secp256k1.PrivKeyFromBytes`](https://pkg.go.dev/github.com/decred/dcrd/dcrec/secp256k1/v4#PrivKeyFromBytes) and comment in `nip44.GenerateConversationkey`:
```
func GenerateConversationKey(sendPrivkey *secp256k1.PrivateKey, recvPubkey *secp256k1.PublicKey) []byte {
// TODO: Make sure keys are not invalid or weak since the secp256k1 package does not.
// See documentation of secp256k1.PrivKeyFromBytes:
// ================================================================================
// | WARNING: This means passing a slice with more than 32 bytes is truncated and |
// | that truncated value is reduced modulo N. Further, 0 is not a valid private |
// | key. It is up to the caller to provide a value in the appropriate range of |
// | [1, N-1]. Failure to do so will either result in an invalid private key or |
// | potentially weak private keys that have bias that could be exploited. |
// ================================================================================
// -- https://pkg.go.dev/github.com/decred/dcrd/dcrec/secp256k1/v4#PrivKeyFromBytes
```
---
NIP-44 specification: https://github.com/nostr-protocol/nips/blob/master/44.md NIP-44 specification: https://github.com/nostr-protocol/nips/blob/master/44.md
To use as library: `go get -u git.ekzyis.com/ekzyis/nip44` To use as library: `go get -u git.ekzyis.com/ekzyis/nip44`

View File

@ -7,10 +7,12 @@ import (
"crypto/sha256" "crypto/sha256"
"encoding/base64" "encoding/base64"
"encoding/binary" "encoding/binary"
"encoding/hex"
"errors" "errors"
"fmt" "fmt"
"io" "io"
"math" "math"
"math/big"
"github.com/decred/dcrd/dcrec/secp256k1/v4" "github.com/decred/dcrd/dcrec/secp256k1/v4"
"golang.org/x/crypto/chacha20" "golang.org/x/crypto/chacha20"
@ -134,19 +136,25 @@ func Decrypt(conversationKey []byte, ciphertext string) (string, error) {
return string(unpadded), nil return string(unpadded), nil
} }
func GenerateConversationKey(sendPrivkey *secp256k1.PrivateKey, recvPubkey *secp256k1.PublicKey) []byte { func GenerateConversationKey(sendPrivkey []byte, recvPubkey []byte) ([]byte, error) {
// TODO: Make sure keys are not invalid or weak since the secp256k1 package does not. var (
// See documentation of secp256k1.PrivKeyFromBytes: N = secp256k1.S256().N
// ================================================================================ sk *secp256k1.PrivateKey
// | WARNING: This means passing a slice with more than 32 bytes is truncated and | pk *secp256k1.PublicKey
// | that truncated value is reduced modulo N. Further, 0 is not a valid private | err error
// | key. It is up to the caller to provide a value in the appropriate range of | )
// | [1, N-1]. Failure to do so will either result in an invalid private key or | // make sure that private key is on curve before using unsafe secp256k1.PrivKeyFromBytes
// | potentially weak private keys that have bias that could be exploited. | // see https://pkg.go.dev/github.com/decred/dcrd/dcrec/secp256k1/v4#PrivKeyFromBytes
// ================================================================================ skX := new(big.Int).SetBytes(sendPrivkey)
// -- https://pkg.go.dev/github.com/decred/dcrd/dcrec/secp256k1/v4#PrivKeyFromBytes if skX.Cmp(big.NewInt(0)) == 0 || skX.Cmp(N) >= 0 {
shared := secp256k1.GenerateSharedSecret(sendPrivkey, recvPubkey) return []byte{}, fmt.Errorf("invalid private key: x coordinate %s is not on the secp256k1 curve", hex.EncodeToString(sendPrivkey))
return hkdf.Extract(sha256.New, shared, []byte("nip44-v2")) }
sk = secp256k1.PrivKeyFromBytes(sendPrivkey)
if pk, err = secp256k1.ParsePubKey(recvPubkey); err != nil {
return []byte{}, err
}
shared := secp256k1.GenerateSharedSecret(sk, pk)
return hkdf.Extract(sha256.New, shared, []byte("nip44-v2")), nil
} }
func chacha20_(key []byte, nonce []byte, message []byte) ([]byte, error) { func chacha20_(key []byte, nonce []byte, message []byte) ([]byte, error) {

View File

@ -94,29 +94,25 @@ func assertDecryptFail(t *testing.T, conversationKey string, plaintext string, c
} }
func assertConversationKeyFail(t *testing.T, sk1 string, pub2 string, msg string) { func assertConversationKeyFail(t *testing.T, sk1 string, pub2 string, msg string) {
// TODO: Update GenerateConversationKey since secp256k1 does accept invalid or weak keys
t.Skip("secp256k1 keys are not validated yet during conversation key generation.")
var ( var (
// sendPrivkey *secp256k1.PrivateKey sk1Decoded []byte
// recvPubkey *secp256k1.PublicKey pub2Decoded []byte
decoded []byte ok bool
ok bool err error
err error
) )
decoded, err = hex.DecodeString(sk1) sk1Decoded, err = hex.DecodeString(sk1)
if ok = assert.NoErrorf(t, err, "hex decode failed for sk1: %v", err); !ok { if ok = assert.NoErrorf(t, err, "hex decode failed for sk1: %v", err); !ok {
return return
} }
// sendPrivkey = secp256k1.PrivKeyFromBytes(decoded) pub2Decoded, err = hex.DecodeString("02" + pub2)
decoded, err = hex.DecodeString("02" + pub2)
if ok = assert.NoErrorf(t, err, "hex decode failed for pub2: %v", err); !ok { if ok = assert.NoErrorf(t, err, "hex decode failed for pub2: %v", err); !ok {
return return
} }
_, err = secp256k1.ParsePubKey(decoded) _, err = nip44.GenerateConversationKey(sk1Decoded, pub2Decoded)
assert.ErrorContains(t, err, msg) assert.ErrorContains(t, err, msg)
} }
func assertConversationKeyGeneration(t *testing.T, sendPrivkey *secp256k1.PrivateKey, recvPubkey *secp256k1.PublicKey, conversationKey string) bool { func assertConversationKeyGeneration(t *testing.T, sendPrivkey []byte, recvPubkey []byte, conversationKey string) bool {
var ( var (
actualConversationKey []byte actualConversationKey []byte
expectedConversationKey []byte expectedConversationKey []byte
@ -127,7 +123,10 @@ func assertConversationKeyGeneration(t *testing.T, sendPrivkey *secp256k1.Privat
if ok = assert.NoErrorf(t, err, "hex decode failed for conversation key: %v", err); !ok { if ok = assert.NoErrorf(t, err, "hex decode failed for conversation key: %v", err); !ok {
return false return false
} }
actualConversationKey = nip44.GenerateConversationKey(sendPrivkey, recvPubkey) actualConversationKey, err = nip44.GenerateConversationKey(sendPrivkey, recvPubkey)
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 { if ok = assert.Equalf(t, expectedConversationKey, actualConversationKey, "wrong conversation key"); !ok {
return false return false
} }
@ -136,41 +135,39 @@ func assertConversationKeyGeneration(t *testing.T, sendPrivkey *secp256k1.Privat
func assertConversationKeyGenerationSec(t *testing.T, sk1 string, sk2 string, conversationKey string) bool { func assertConversationKeyGenerationSec(t *testing.T, sk1 string, sk2 string, conversationKey string) bool {
var ( var (
sendPrivkey *secp256k1.PrivateKey sk1Decoded []byte
recvPubkey *secp256k1.PublicKey pub2Decoded []byte
ok bool ok bool
err error err error
) )
if decoded, err := hex.DecodeString(sk1); err == nil { sk1Decoded, err = hex.DecodeString(sk1)
sendPrivkey = secp256k1.PrivKeyFromBytes(decoded)
}
if ok = assert.NoErrorf(t, err, "hex decode failed for sk1: %v", err); !ok { if ok = assert.NoErrorf(t, err, "hex decode failed for sk1: %v", err); !ok {
return false return false
} }
if decoded, err := hex.DecodeString(sk2); err == nil { if decoded, err := hex.DecodeString(sk2); err == nil {
recvPubkey = secp256k1.PrivKeyFromBytes(decoded).PubKey() pub2Decoded = secp256k1.PrivKeyFromBytes(decoded).PubKey().SerializeCompressed()
} }
if ok = assert.NoErrorf(t, err, "hex decode failed for sk2: %v", err); !ok { if ok = assert.NoErrorf(t, err, "hex decode failed for sk2: %v", err); !ok {
return false return false
} }
return assertConversationKeyGeneration(t, sendPrivkey, recvPubkey, conversationKey) return assertConversationKeyGeneration(t, sk1Decoded, pub2Decoded, conversationKey)
} }
func assertConversationKeyGenerationPub(t *testing.T, sk1 string, pub2 string, conversationKey string) bool { func assertConversationKeyGenerationPub(t *testing.T, sk1 string, pub2 string, conversationKey string) bool {
var ( var (
sendPrivkey *secp256k1.PrivateKey sk1Decoded []byte
recvPubkey *secp256k1.PublicKey pub2Decoded []byte
ok bool ok bool
err error err error
) )
if decoded, err := hex.DecodeString(sk1); err == nil { sk1Decoded, err = hex.DecodeString(sk1)
sendPrivkey = secp256k1.PrivKeyFromBytes(decoded)
}
if ok = assert.NoErrorf(t, err, "hex decode failed for sk1: %v", err); !ok { if ok = assert.NoErrorf(t, err, "hex decode failed for sk1: %v", err); !ok {
return false return false
} }
if decoded, err := hex.DecodeString("02" + pub2); err == nil { if decoded, err := hex.DecodeString("02" + pub2); err == nil {
recvPubkey, err = secp256k1.ParsePubKey(decoded) if recvPubkey, err := secp256k1.ParsePubKey(decoded); err == nil {
pub2Decoded = recvPubkey.SerializeCompressed()
}
if ok = assert.NoErrorf(t, err, "parse pubkey failed: %v", err); !ok { if ok = assert.NoErrorf(t, err, "parse pubkey failed: %v", err); !ok {
return false return false
} }
@ -178,7 +175,7 @@ func assertConversationKeyGenerationPub(t *testing.T, sk1 string, pub2 string, c
if ok = assert.NoErrorf(t, err, "hex decode failed for pub2: %v", err); !ok { if ok = assert.NoErrorf(t, err, "hex decode failed for pub2: %v", err); !ok {
return false return false
} }
return assertConversationKeyGeneration(t, sendPrivkey, recvPubkey, conversationKey) return assertConversationKeyGeneration(t, sk1Decoded, pub2Decoded, 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 {
@ -431,7 +428,7 @@ func TestConversationKeyFail001(t *testing.T) {
assertConversationKeyFail(t, assertConversationKeyFail(t,
"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
"1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef", "1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef",
"x coordinate 1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef is not on the secp256k1 curve", "invalid private key: x coordinate ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff is not on the secp256k1 curve",
) )
} }
@ -440,7 +437,7 @@ func TestConversationKeyFail002(t *testing.T) {
assertConversationKeyFail(t, assertConversationKeyFail(t,
"0000000000000000000000000000000000000000000000000000000000000000", "0000000000000000000000000000000000000000000000000000000000000000",
"1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef", "1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef",
"x coordinate 1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef is not on the secp256k1 curve", "invalid private key: x coordinate 0000000000000000000000000000000000000000000000000000000000000000 is not on the secp256k1 curve",
) )
} }
@ -449,7 +446,7 @@ func TestConversationKeyFail003(t *testing.T) {
assertConversationKeyFail(t, assertConversationKeyFail(t,
"fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364139", "fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364139",
"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
"x >= field prime", "invalid public key: x >= field prime",
) )
} }
@ -458,7 +455,7 @@ func TestConversationKeyFail004(t *testing.T) {
assertConversationKeyFail(t, assertConversationKeyFail(t,
"fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141", "fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141",
"1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef", "1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef",
"x coordinate 1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef is not on the secp256k1 curve", "invalid private key: x coordinate fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141 is not on the secp256k1 curve",
) )
} }
@ -467,7 +464,7 @@ func TestConversationKeyFail005(t *testing.T) {
assertConversationKeyFail(t, assertConversationKeyFail(t,
"0000000000000000000000000000000000000000000000000000000000000002", "0000000000000000000000000000000000000000000000000000000000000002",
"1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef", "1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef",
"x coordinate 1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef is not on the secp256k1 curve", "invalid public key: x coordinate 1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef is not on the secp256k1 curve",
) )
} }
@ -476,7 +473,7 @@ func TestConversationKeyFail006(t *testing.T) {
assertConversationKeyFail(t, assertConversationKeyFail(t,
"0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20", "0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20",
"0000000000000000000000000000000000000000000000000000000000000000", "0000000000000000000000000000000000000000000000000000000000000000",
"x coordinate 0000000000000000000000000000000000000000000000000000000000000000 is not on the secp256k1 curve", "invalid public key: x coordinate 0000000000000000000000000000000000000000000000000000000000000000 is not on the secp256k1 curve",
) )
} }
@ -485,7 +482,7 @@ func TestConversationKeyFail007(t *testing.T) {
assertConversationKeyFail(t, assertConversationKeyFail(t,
"0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20", "0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20",
"eb1f7200aecaa86682376fb1c13cd12b732221e774f553b0a0857f88fa20f86d", "eb1f7200aecaa86682376fb1c13cd12b732221e774f553b0a0857f88fa20f86d",
"x coordinate eb1f7200aecaa86682376fb1c13cd12b732221e774f553b0a0857f88fa20f86d is not on the secp256k1 curve", "invalid public key: x coordinate eb1f7200aecaa86682376fb1c13cd12b732221e774f553b0a0857f88fa20f86d is not on the secp256k1 curve",
) )
} }
@ -494,7 +491,7 @@ func TestConversationKeyFail008(t *testing.T) {
assertConversationKeyFail(t, assertConversationKeyFail(t,
"0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20", "0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20",
"709858a4c121e4a84eb59c0ded0261093c71e8ca29efeef21a6161c447bcaf9f", "709858a4c121e4a84eb59c0ded0261093c71e8ca29efeef21a6161c447bcaf9f",
"x coordinate 709858a4c121e4a84eb59c0ded0261093c71e8ca29efeef21a6161c447bcaf9f is not on the secp256k1 curve", "invalid public key: x coordinate 709858a4c121e4a84eb59c0ded0261093c71e8ca29efeef21a6161c447bcaf9f is not on the secp256k1 curve",
) )
} }