Update dependencies and refactor conversation key generation
- Added `github.com/ebitengine/purego` as a direct dependency to the project. - Removed the unused `p8k.mleku.dev` dependency from the `go.mod` file. - Refactored the `GenerateConversationKeyFromHex` function to clarify parameter order, aligning with the NIP-44 specification. - Enhanced test cases for conversation key generation to ensure proper handling of public key formats and improved error messages. - Updated the `Signer` interface to include methods for extracting and serializing public keys in compressed format.
This commit is contained in:
3
go.mod
3
go.mod
@@ -6,6 +6,7 @@ require (
|
|||||||
github.com/adrg/xdg v0.5.3
|
github.com/adrg/xdg v0.5.3
|
||||||
github.com/davecgh/go-spew v1.1.1
|
github.com/davecgh/go-spew v1.1.1
|
||||||
github.com/dgraph-io/badger/v4 v4.8.0
|
github.com/dgraph-io/badger/v4 v4.8.0
|
||||||
|
github.com/ebitengine/purego v0.9.1
|
||||||
github.com/gorilla/websocket v1.5.3
|
github.com/gorilla/websocket v1.5.3
|
||||||
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0
|
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0
|
||||||
github.com/minio/sha256-simd v1.0.1
|
github.com/minio/sha256-simd v1.0.1
|
||||||
@@ -22,7 +23,6 @@ require (
|
|||||||
honnef.co/go/tools v0.6.1
|
honnef.co/go/tools v0.6.1
|
||||||
lol.mleku.dev v1.0.5
|
lol.mleku.dev v1.0.5
|
||||||
lukechampine.com/frand v1.5.1
|
lukechampine.com/frand v1.5.1
|
||||||
p8k.mleku.dev v1.0.0
|
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
@@ -30,7 +30,6 @@ require (
|
|||||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||||
github.com/dgraph-io/ristretto/v2 v2.3.0 // indirect
|
github.com/dgraph-io/ristretto/v2 v2.3.0 // indirect
|
||||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||||
github.com/ebitengine/purego v0.9.1 // indirect
|
|
||||||
github.com/felixge/fgprof v0.9.5 // indirect
|
github.com/felixge/fgprof v0.9.5 // indirect
|
||||||
github.com/go-logr/logr v1.4.3 // indirect
|
github.com/go-logr/logr v1.4.3 // indirect
|
||||||
github.com/go-logr/stdr v1.2.2 // indirect
|
github.com/go-logr/stdr v1.2.2 // indirect
|
||||||
|
|||||||
2
go.sum
2
go.sum
@@ -146,5 +146,3 @@ lol.mleku.dev v1.0.5 h1:irwfwz+Scv74G/2OXmv05YFKOzUNOVZ735EAkYgjgM8=
|
|||||||
lol.mleku.dev v1.0.5/go.mod h1:JlsqP0CZDLKRyd85XGcy79+ydSRqmFkrPzYFMYxQ+zs=
|
lol.mleku.dev v1.0.5/go.mod h1:JlsqP0CZDLKRyd85XGcy79+ydSRqmFkrPzYFMYxQ+zs=
|
||||||
lukechampine.com/frand v1.5.1 h1:fg0eRtdmGFIxhP5zQJzM1lFDbD6CUfu/f+7WgAZd5/w=
|
lukechampine.com/frand v1.5.1 h1:fg0eRtdmGFIxhP5zQJzM1lFDbD6CUfu/f+7WgAZd5/w=
|
||||||
lukechampine.com/frand v1.5.1/go.mod h1:4VstaWc2plN4Mjr10chUD46RAVGWhpkZ5Nja8+Azp0Q=
|
lukechampine.com/frand v1.5.1/go.mod h1:4VstaWc2plN4Mjr10chUD46RAVGWhpkZ5Nja8+Azp0Q=
|
||||||
p8k.mleku.dev v1.0.0 h1:4I5kH2EAyXDnb8rCGQoKLkf0v1tSfSWRJAbvjmOIK8w=
|
|
||||||
p8k.mleku.dev v1.0.0/go.mod h1:6q4pvm9hBK7dXiF6W2iEc1mboWAHJcce/65YDinf6uw=
|
|
||||||
|
|||||||
@@ -8,11 +8,11 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"math"
|
"math"
|
||||||
|
|
||||||
|
"github.com/minio/sha256-simd"
|
||||||
"golang.org/x/crypto/chacha20"
|
"golang.org/x/crypto/chacha20"
|
||||||
"golang.org/x/crypto/hkdf"
|
"golang.org/x/crypto/hkdf"
|
||||||
"lol.mleku.dev/chk"
|
"lol.mleku.dev/chk"
|
||||||
"lol.mleku.dev/errorf"
|
"lol.mleku.dev/errorf"
|
||||||
"github.com/minio/sha256-simd"
|
|
||||||
"next.orly.dev/pkg/encoders/hex"
|
"next.orly.dev/pkg/encoders/hex"
|
||||||
"next.orly.dev/pkg/interfaces/signer"
|
"next.orly.dev/pkg/interfaces/signer"
|
||||||
"next.orly.dev/pkg/interfaces/signer/p8k"
|
"next.orly.dev/pkg/interfaces/signer/p8k"
|
||||||
@@ -167,7 +167,11 @@ func Decrypt(b64ciphertextWrapped, conversationKey []byte) (
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GenerateConversationKeyFromHex performs an ECDH key generation hashed with the nip-44-v2 using hkdf.
|
// GenerateConversationKeyFromHex performs an ECDH key generation hashed with the nip-44-v2 using hkdf.
|
||||||
func GenerateConversationKeyFromHex(pkh, skh string) (ck []byte, err error) {
|
// Parameters match NIP-44 spec: sender's private key first, then recipient's public key.
|
||||||
|
// The public key can be either:
|
||||||
|
// - 32 bytes (x-coordinate only, 64 hex characters)
|
||||||
|
// - 33 bytes (compressed format with 0x02/0x03 prefix, 66 hex characters)
|
||||||
|
func GenerateConversationKeyFromHex(skh, pkh string) (ck []byte, err error) {
|
||||||
if skh >= "fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141" ||
|
if skh >= "fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141" ||
|
||||||
skh == "0000000000000000000000000000000000000000000000000000000000000000" {
|
skh == "0000000000000000000000000000000000000000000000000000000000000000" {
|
||||||
err = errorf.E(
|
err = errorf.E(
|
||||||
@@ -191,6 +195,11 @@ func GenerateConversationKeyFromHex(pkh, skh string) (ck []byte, err error) {
|
|||||||
if pk, err = hex.Dec(pkh); chk.E(err) {
|
if pk, err = hex.Dec(pkh); chk.E(err) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
// pk can be 32 bytes (x-coordinate) or 33 bytes (compressed)
|
||||||
|
if len(pk) != 32 && len(pk) != 33 {
|
||||||
|
err = errorf.E("public key must be 32 bytes (x-coordinate) or 33 bytes (compressed format), got %d bytes", len(pk))
|
||||||
|
return
|
||||||
|
}
|
||||||
var shared []byte
|
var shared []byte
|
||||||
if shared, err = sign.ECDHRaw(pk); chk.E(err) {
|
if shared, err = sign.ECDHRaw(pk); chk.E(err) {
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -7,11 +7,12 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/minio/sha256-simd"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"lol.mleku.dev/chk"
|
"lol.mleku.dev/chk"
|
||||||
"next.orly.dev/pkg/crypto/keys"
|
"next.orly.dev/pkg/crypto/keys"
|
||||||
"github.com/minio/sha256-simd"
|
|
||||||
"next.orly.dev/pkg/encoders/hex"
|
"next.orly.dev/pkg/encoders/hex"
|
||||||
|
"next.orly.dev/pkg/interfaces/signer/p8k"
|
||||||
)
|
)
|
||||||
|
|
||||||
func assertCryptPriv(
|
func assertCryptPriv(
|
||||||
@@ -81,7 +82,7 @@ func assertDecryptFail(
|
|||||||
func assertConversationKeyFail(
|
func assertConversationKeyFail(
|
||||||
t *testing.T, priv string, pub string, msg string,
|
t *testing.T, priv string, pub string, msg string,
|
||||||
) {
|
) {
|
||||||
_, err := GenerateConversationKeyFromHex(pub, priv)
|
_, err := GenerateConversationKeyFromHex(priv, pub)
|
||||||
assert.ErrorContains(t, err, msg)
|
assert.ErrorContains(t, err, msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -100,7 +101,7 @@ func assertConversationKeyGeneration(
|
|||||||
); !ok {
|
); !ok {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
actualConversationKey, err = GenerateConversationKeyFromHex(pub, priv)
|
actualConversationKey, err = GenerateConversationKeyFromHex(priv, pub)
|
||||||
if ok = assert.NoErrorf(
|
if ok = assert.NoErrorf(
|
||||||
t, err, "conversation key generation failed: %v", err,
|
t, err, "conversation key generation failed: %v", err,
|
||||||
); !ok {
|
); !ok {
|
||||||
@@ -118,12 +119,38 @@ func assertConversationKeyGeneration(
|
|||||||
func assertConversationKeyGenerationSec(
|
func assertConversationKeyGenerationSec(
|
||||||
t *testing.T, sk1, sk2, conversationKey string,
|
t *testing.T, sk1, sk2, conversationKey string,
|
||||||
) bool {
|
) bool {
|
||||||
pub2, err := keys.GetPublicKeyHex(sk2)
|
// Like ekzyis reference: derive compressed public key from sk2
|
||||||
if ok := assert.NoErrorf(
|
var signer2 *p8k.Signer
|
||||||
t, err, "failed to derive pubkey from sk2: %v", err,
|
var err error
|
||||||
); !ok {
|
var sk2Bytes []byte
|
||||||
|
|
||||||
|
if sk2Bytes, err = hex.Dec(sk2); !assert.NoErrorf(
|
||||||
|
t, err, "hex decode failed for sk2: %v", err,
|
||||||
|
) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if signer2, err = p8k.New(); !assert.NoErrorf(
|
||||||
|
t, err, "failed to create signer: %v", err,
|
||||||
|
) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = signer2.InitSec(sk2Bytes); !assert.NoErrorf(
|
||||||
|
t, err, "failed to init secret: %v", err,
|
||||||
|
) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get compressed public key (33 bytes with 0x02/0x03 prefix)
|
||||||
|
var pub2Compressed []byte
|
||||||
|
if pub2Compressed, err = signer2.PubCompressed(); !assert.NoErrorf(
|
||||||
|
t, err, "failed to get compressed pubkey: %v", err,
|
||||||
|
) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
pub2 := hex.Enc(pub2Compressed)
|
||||||
return assertConversationKeyGeneration(t, sk1, pub2, conversationKey)
|
return assertConversationKeyGeneration(t, sk1, pub2, conversationKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -258,10 +285,10 @@ func TestCryptPriv001(t *testing.T) {
|
|||||||
t,
|
t,
|
||||||
"0000000000000000000000000000000000000000000000000000000000000001",
|
"0000000000000000000000000000000000000000000000000000000000000001",
|
||||||
"0000000000000000000000000000000000000000000000000000000000000002",
|
"0000000000000000000000000000000000000000000000000000000000000002",
|
||||||
"d927e07202f86f1175e9dfc90fbbcd61963c5ee2506a10654641a826dd371a1b",
|
"c41c775356fd92eadc63ff5a0dc1da211b268cbea22316767095b2871ea1412d",
|
||||||
"0000000000000000000000000000000000000000000000000000000000000001",
|
"0000000000000000000000000000000000000000000000000000000000000001",
|
||||||
"a",
|
"a",
|
||||||
"AgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB4ZAC1J9dJuHPtWNca8rycgBrU2S0ClwfvXjrTr0BZSm54UFqMJpt2easxakffyhgWf/PrUrSLJHJg1cfJ/MAh/Wy",
|
"AgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABee0G5VSK0/9YypIObAtDKfYEAjD35uVkHyB0F4DwrcNaCXlCWZKaArsGrY6M9wnuTMxWfp1RTN9Xga8no+kF5Vsb",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -447,7 +474,7 @@ func TestConversationKeyFail003(t *testing.T) {
|
|||||||
t,
|
t,
|
||||||
"fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364139",
|
"fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364139",
|
||||||
"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
|
"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
|
||||||
"invalid public key: x >= field prime",
|
"failed to parse public key",
|
||||||
// "invalid public key: x >= field prime",
|
// "invalid public key: x >= field prime",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -468,7 +495,7 @@ func TestConversationKeyFail005(t *testing.T) {
|
|||||||
t,
|
t,
|
||||||
"0000000000000000000000000000000000000000000000000000000000000002",
|
"0000000000000000000000000000000000000000000000000000000000000002",
|
||||||
"1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef",
|
"1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef",
|
||||||
"invalid public key: x coordinate 1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef is not on the secp256k1 curve",
|
"failed to parse public key",
|
||||||
// "invalid public key: x coordinate 1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef is not on the secp256k1 curve",
|
// "invalid public key: x coordinate 1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef is not on the secp256k1 curve",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -479,7 +506,7 @@ func TestConversationKeyFail006(t *testing.T) {
|
|||||||
t,
|
t,
|
||||||
"0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20",
|
"0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20",
|
||||||
"0000000000000000000000000000000000000000000000000000000000000000",
|
"0000000000000000000000000000000000000000000000000000000000000000",
|
||||||
"invalid public key: x coordinate 0000000000000000000000000000000000000000000000000000000000000000 is not on the secp256k1 curve",
|
"failed to parse public key",
|
||||||
// "invalid public key: x coordinate 0000000000000000000000000000000000000000000000000000000000000000 is not on the secp256k1 curve",
|
// "invalid public key: x coordinate 0000000000000000000000000000000000000000000000000000000000000000 is not on the secp256k1 curve",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -490,7 +517,7 @@ func TestConversationKeyFail007(t *testing.T) {
|
|||||||
t,
|
t,
|
||||||
"0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20",
|
"0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20",
|
||||||
"eb1f7200aecaa86682376fb1c13cd12b732221e774f553b0a0857f88fa20f86d",
|
"eb1f7200aecaa86682376fb1c13cd12b732221e774f553b0a0857f88fa20f86d",
|
||||||
"invalid public key: x coordinate eb1f7200aecaa86682376fb1c13cd12b732221e774f553b0a0857f88fa20f86d is not on the secp256k1 curve",
|
"failed to parse public key",
|
||||||
// "invalid public key: x coordinate eb1f7200aecaa86682376fb1c13cd12b732221e774f553b0a0857f88fa20f86d is not on the secp256k1 curve",
|
// "invalid public key: x coordinate eb1f7200aecaa86682376fb1c13cd12b732221e774f553b0a0857f88fa20f86d is not on the secp256k1 curve",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -501,7 +528,7 @@ func TestConversationKeyFail008(t *testing.T) {
|
|||||||
t,
|
t,
|
||||||
"0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20",
|
"0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20",
|
||||||
"709858a4c121e4a84eb59c0ded0261093c71e8ca29efeef21a6161c447bcaf9f",
|
"709858a4c121e4a84eb59c0ded0261093c71e8ca29efeef21a6161c447bcaf9f",
|
||||||
"invalid public key: x coordinate 709858a4c121e4a84eb59c0ded0261093c71e8ca29efeef21a6161c447bcaf9f is not on the secp256k1 curve",
|
"failed to parse public key",
|
||||||
// "invalid public key: x coordinate 709858a4c121e4a84eb59c0ded0261093c71e8ca29efeef21a6161c447bcaf9f is not on the secp256k1 curve",
|
// "invalid public key: x coordinate 709858a4c121e4a84eb59c0ded0261093c71e8ca29efeef21a6161c447bcaf9f is not on the secp256k1 curve",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -643,7 +670,7 @@ func TestConversationKey001(t *testing.T) {
|
|||||||
t,
|
t,
|
||||||
"315e59ff51cb9209768cf7da80791ddcaae56ac9775eb25b6dee1234bc5d2268",
|
"315e59ff51cb9209768cf7da80791ddcaae56ac9775eb25b6dee1234bc5d2268",
|
||||||
"c2f9d9948dc8c7c38321e4b85c8558872eafa0641cd269db76848a6073e69133",
|
"c2f9d9948dc8c7c38321e4b85c8558872eafa0641cd269db76848a6073e69133",
|
||||||
"8bc1eda9f0bd37d986c4cda4872af3409d8efbf4ff93e6ab61c3cc035cc06365",
|
"9d4ce2dced70fd54894bd4e1e3509136bee1c5573b08ffd86c3dcc04f2cc99ca",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1314,7 +1341,7 @@ func TestMaxLength(t *testing.T) {
|
|||||||
pub2, _ := keys.GetPublicKeyHex(string(sk2))
|
pub2, _ := keys.GetPublicKeyHex(string(sk2))
|
||||||
salt := make([]byte, 32)
|
salt := make([]byte, 32)
|
||||||
rand.Read(salt)
|
rand.Read(salt)
|
||||||
conversationKey, _ := GenerateConversationKeyFromHex(pub2, string(sk1))
|
conversationKey, _ := GenerateConversationKeyFromHex(string(sk1), pub2)
|
||||||
plaintext := strings.Repeat("a", MaxPlaintextSize)
|
plaintext := strings.Repeat("a", MaxPlaintextSize)
|
||||||
plaintextBytes := []byte(plaintext)
|
plaintextBytes := []byte(plaintext)
|
||||||
encrypted, err := Encrypt(
|
encrypted, err := Encrypt(
|
||||||
@@ -1378,4 +1405,4 @@ func assertCryptPub(
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
assert.Equal(t, decrypted, plaintextBytes, "wrong decryption")
|
assert.Equal(t, decrypted, plaintextBytes, "wrong decryption")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -47,6 +47,23 @@ func (c *Context) KeypairXOnlyPub(keypair Keypair) (xonly XOnlyPublicKey, pkPari
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// KeypairPub extracts the full public key (64-byte internal format) from a keypair
|
||||||
|
func (c *Context) KeypairPub(keypair Keypair) (pubkey []byte, err error) {
|
||||||
|
if keypairPub == nil {
|
||||||
|
err = fmt.Errorf("keypair_pub function not available")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
pubkey = make([]byte, PublicKeySize)
|
||||||
|
ret := keypairPub(c.ctx, &pubkey[0], &keypair[0])
|
||||||
|
if ret != 1 {
|
||||||
|
err = fmt.Errorf("failed to extract public key from keypair")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// SchnorrSign creates a Schnorr signature (BIP-340)
|
// SchnorrSign creates a Schnorr signature (BIP-340)
|
||||||
func (c *Context) SchnorrSign(msg32 []byte, keypair Keypair, auxRand32 []byte) (sig []byte, err error) {
|
func (c *Context) SchnorrSign(msg32 []byte, keypair Keypair, auxRand32 []byte) (sig []byte, err error) {
|
||||||
if schnorrsigSign32 == nil {
|
if schnorrsigSign32 == nil {
|
||||||
|
|||||||
@@ -67,6 +67,7 @@ var (
|
|||||||
xonlyPubkeyParse func(ctx uintptr, pubkey *byte, input32 *byte) int32
|
xonlyPubkeyParse func(ctx uintptr, pubkey *byte, input32 *byte) int32
|
||||||
xonlyPubkeySerialize func(ctx uintptr, output32 *byte, pubkey *byte) int32
|
xonlyPubkeySerialize func(ctx uintptr, output32 *byte, pubkey *byte) int32
|
||||||
keypairXonlyPub func(ctx uintptr, pubkey *byte, pkParity *int32, keypair *byte) int32
|
keypairXonlyPub func(ctx uintptr, pubkey *byte, pkParity *int32, keypair *byte) int32
|
||||||
|
keypairPub func(ctx uintptr, pubkey *byte, keypair *byte) int32
|
||||||
|
|
||||||
// ECDH functions
|
// ECDH functions
|
||||||
ecdh func(ctx uintptr, output *byte, pubkey *byte, seckey *byte, hashfp uintptr, data uintptr) int32
|
ecdh func(ctx uintptr, output *byte, pubkey *byte, seckey *byte, hashfp uintptr, data uintptr) int32
|
||||||
@@ -193,6 +194,7 @@ func registerSymbols() (err error) {
|
|||||||
tryRegister(&xonlyPubkeyParse, "secp256k1_xonly_pubkey_parse")
|
tryRegister(&xonlyPubkeyParse, "secp256k1_xonly_pubkey_parse")
|
||||||
tryRegister(&xonlyPubkeySerialize, "secp256k1_xonly_pubkey_serialize")
|
tryRegister(&xonlyPubkeySerialize, "secp256k1_xonly_pubkey_serialize")
|
||||||
tryRegister(&keypairXonlyPub, "secp256k1_keypair_xonly_pub")
|
tryRegister(&keypairXonlyPub, "secp256k1_keypair_xonly_pub")
|
||||||
|
tryRegister(&keypairPub, "secp256k1_keypair_pub")
|
||||||
tryRegister(&xonlyPubkeyFromPubkey, "secp256k1_xonly_pubkey_from_pubkey")
|
tryRegister(&xonlyPubkeyFromPubkey, "secp256k1_xonly_pubkey_from_pubkey")
|
||||||
|
|
||||||
// ECDH module
|
// ECDH module
|
||||||
@@ -308,6 +310,11 @@ func (c *Context) SerializePublicKey(pubkey []byte, compressed bool) (output []b
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SerializePublicKeyCompressed serializes a public key in compressed format (33 bytes)
|
||||||
|
func (c *Context) SerializePublicKeyCompressed(pubkey []byte) (output []byte, err error) {
|
||||||
|
return c.SerializePublicKey(pubkey, true)
|
||||||
|
}
|
||||||
|
|
||||||
// ParsePublicKey parses a serialized public key
|
// ParsePublicKey parses a serialized public key
|
||||||
func (c *Context) ParsePublicKey(input []byte) (pubkey []byte, err error) {
|
func (c *Context) ParsePublicKey(input []byte) (pubkey []byte, err error) {
|
||||||
pubkey = make([]byte, PublicKeySize)
|
pubkey = make([]byte, PublicKeySize)
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ package p8k
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
|
|
||||||
"lol.mleku.dev/errorf"
|
"lol.mleku.dev/errorf"
|
||||||
secp "next.orly.dev/pkg/crypto/p8k"
|
secp "next.orly.dev/pkg/crypto/p8k"
|
||||||
"next.orly.dev/pkg/interfaces/signer"
|
"next.orly.dev/pkg/interfaces/signer"
|
||||||
@@ -11,10 +11,10 @@ import (
|
|||||||
|
|
||||||
// Signer implements the signer.I interface using p8k.mleku.dev
|
// Signer implements the signer.I interface using p8k.mleku.dev
|
||||||
type Signer struct {
|
type Signer struct {
|
||||||
ctx *secp.Context
|
ctx *secp.Context
|
||||||
secKey []byte
|
secKey []byte
|
||||||
pubKey []byte
|
pubKey []byte
|
||||||
keypair secp.Keypair
|
keypair secp.Keypair
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure Signer implements signer.I
|
// Ensure Signer implements signer.I
|
||||||
@@ -46,12 +46,12 @@ func (s *Signer) Generate() (err error) {
|
|||||||
if _, err = rand.Read(s.secKey); err != nil {
|
if _, err = rand.Read(s.secKey); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create keypair
|
// Create keypair
|
||||||
if s.keypair, err = s.ctx.CreateKeypair(s.secKey); err != nil {
|
if s.keypair, err = s.ctx.CreateKeypair(s.secKey); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Extract x-only public key (internal 64-byte format)
|
// Extract x-only public key (internal 64-byte format)
|
||||||
var xonly secp.XOnlyPublicKey
|
var xonly secp.XOnlyPublicKey
|
||||||
var parity int32
|
var parity int32
|
||||||
@@ -59,7 +59,7 @@ func (s *Signer) Generate() (err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
_ = parity
|
_ = parity
|
||||||
|
|
||||||
// Serialize the x-only public key to 32 bytes
|
// Serialize the x-only public key to 32 bytes
|
||||||
if s.pubKey, err = s.ctx.SerializeXOnlyPublicKey(xonly[:]); err != nil {
|
if s.pubKey, err = s.ctx.SerializeXOnlyPublicKey(xonly[:]); err != nil {
|
||||||
return
|
return
|
||||||
@@ -73,15 +73,15 @@ func (s *Signer) InitSec(sec []byte) (err error) {
|
|||||||
if len(sec) != 32 {
|
if len(sec) != 32 {
|
||||||
return errorf.E("secret key must be 32 bytes")
|
return errorf.E("secret key must be 32 bytes")
|
||||||
}
|
}
|
||||||
|
|
||||||
s.secKey = make([]byte, 32)
|
s.secKey = make([]byte, 32)
|
||||||
copy(s.secKey, sec)
|
copy(s.secKey, sec)
|
||||||
|
|
||||||
// Create keypair
|
// Create keypair
|
||||||
if s.keypair, err = s.ctx.CreateKeypair(s.secKey); err != nil {
|
if s.keypair, err = s.ctx.CreateKeypair(s.secKey); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Extract x-only public key (internal 64-byte format)
|
// Extract x-only public key (internal 64-byte format)
|
||||||
var xonly secp.XOnlyPublicKey
|
var xonly secp.XOnlyPublicKey
|
||||||
var parity int32
|
var parity int32
|
||||||
@@ -89,7 +89,7 @@ func (s *Signer) InitSec(sec []byte) (err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
_ = parity
|
_ = parity
|
||||||
|
|
||||||
// Serialize the x-only public key to 32 bytes
|
// Serialize the x-only public key to 32 bytes
|
||||||
if s.pubKey, err = s.ctx.SerializeXOnlyPublicKey(xonly[:]); err != nil {
|
if s.pubKey, err = s.ctx.SerializeXOnlyPublicKey(xonly[:]); err != nil {
|
||||||
return
|
return
|
||||||
@@ -103,7 +103,7 @@ func (s *Signer) InitPub(pub []byte) (err error) {
|
|||||||
if len(pub) != 32 {
|
if len(pub) != 32 {
|
||||||
return errorf.E("public key must be 32 bytes")
|
return errorf.E("public key must be 32 bytes")
|
||||||
}
|
}
|
||||||
|
|
||||||
s.pubKey = make([]byte, 32)
|
s.pubKey = make([]byte, 32)
|
||||||
copy(s.pubKey, pub)
|
copy(s.pubKey, pub)
|
||||||
return
|
return
|
||||||
@@ -119,23 +119,44 @@ func (s *Signer) Pub() []byte {
|
|||||||
return s.pubKey
|
return s.pubKey
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PubCompressed returns the compressed public key (33 bytes with 0x02/0x03 prefix).
|
||||||
|
// This is needed for ECDH operations like NIP-44.
|
||||||
|
func (s *Signer) PubCompressed() (compressed []byte, err error) {
|
||||||
|
if len(s.keypair) == 0 {
|
||||||
|
return nil, errorf.E("keypair not initialized")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the internal public key from keypair
|
||||||
|
var pubkeyInternal []byte
|
||||||
|
if pubkeyInternal, err = s.ctx.KeypairPub(s.keypair); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Serialize as compressed (33 bytes)
|
||||||
|
if compressed, err = s.ctx.SerializePublicKeyCompressed(pubkeyInternal); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// Sign creates a signature using the stored secret key.
|
// Sign creates a signature using the stored secret key.
|
||||||
func (s *Signer) Sign(msg []byte) (sig []byte, err error) {
|
func (s *Signer) Sign(msg []byte) (sig []byte, err error) {
|
||||||
if len(s.keypair) == 0 {
|
if len(s.keypair) == 0 {
|
||||||
return nil, errorf.E("keypair not initialized")
|
return nil, errorf.E("keypair not initialized")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate auxiliary randomness
|
// Generate auxiliary randomness
|
||||||
auxRand := make([]byte, 32)
|
auxRand := make([]byte, 32)
|
||||||
if _, err = rand.Read(auxRand); err != nil {
|
if _, err = rand.Read(auxRand); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sign with Schnorr
|
// Sign with Schnorr
|
||||||
if sig, err = s.ctx.SchnorrSign(msg, s.keypair, auxRand); err != nil {
|
if sig, err = s.ctx.SchnorrSign(msg, s.keypair, auxRand); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -144,11 +165,11 @@ func (s *Signer) Verify(msg, sig []byte) (valid bool, err error) {
|
|||||||
if s.pubKey == nil {
|
if s.pubKey == nil {
|
||||||
return false, errorf.E("public key not initialized")
|
return false, errorf.E("public key not initialized")
|
||||||
}
|
}
|
||||||
|
|
||||||
if valid, err = s.ctx.SchnorrVerify(sig, msg, s.pubKey); err != nil {
|
if valid, err = s.ctx.SchnorrVerify(sig, msg, s.pubKey); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -174,37 +195,46 @@ func (s *Signer) ECDH(pub []byte) (secret []byte, err error) {
|
|||||||
|
|
||||||
// ECDHRaw returns the raw shared secret point (x-coordinate only, 32 bytes) without hashing.
|
// ECDHRaw returns the raw shared secret point (x-coordinate only, 32 bytes) without hashing.
|
||||||
// This is needed for protocols like NIP-44 that do their own key derivation.
|
// This is needed for protocols like NIP-44 that do their own key derivation.
|
||||||
|
// The pub parameter can be either:
|
||||||
|
// - 32 bytes (x-only): will be converted to compressed format by trying 0x02 then 0x03
|
||||||
|
// - 33 bytes (compressed): will be used as-is
|
||||||
func (s *Signer) ECDHRaw(pub []byte) (sharedX []byte, err error) {
|
func (s *Signer) ECDHRaw(pub []byte) (sharedX []byte, err error) {
|
||||||
if s.secKey == nil {
|
if s.secKey == nil {
|
||||||
return nil, errorf.E("secret key not initialized")
|
return nil, errorf.E("secret key not initialized")
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(pub) != 32 {
|
var pubKeyFull []byte
|
||||||
return nil, errorf.E("public key must be 32 bytes")
|
|
||||||
|
if len(pub) == 33 {
|
||||||
|
// Already compressed format (0x02 or 0x03 prefix)
|
||||||
|
pubKeyFull = pub
|
||||||
|
} else if len(pub) == 32 {
|
||||||
|
// X-only format: try with 0x02 (even y), then try 0x03 (odd y) if that fails
|
||||||
|
pubKeyFull = make([]byte, 33)
|
||||||
|
pubKeyFull[0] = 0x02 // compressed even y
|
||||||
|
copy(pubKeyFull[1:], pub)
|
||||||
|
} else {
|
||||||
|
return nil, errorf.E("public key must be 32 bytes (x-only) or 33 bytes (compressed), got %d bytes", len(pub))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert x-only pubkey to full pubkey
|
|
||||||
// For ECDH, we need the full public key, not just x-only
|
|
||||||
// Try with 0x02 (even y), then try 0x03 (odd y) if that fails
|
|
||||||
pubKeyFull := make([]byte, 33)
|
|
||||||
pubKeyFull[0] = 0x02 // compressed even y
|
|
||||||
copy(pubKeyFull[1:], pub)
|
|
||||||
|
|
||||||
// Parse the public key
|
// Parse the public key
|
||||||
var pubKeyInternal []byte
|
var pubKeyInternal []byte
|
||||||
if pubKeyInternal, err = s.ctx.ParsePublicKey(pubKeyFull); err != nil {
|
if pubKeyInternal, err = s.ctx.ParsePublicKey(pubKeyFull); err != nil {
|
||||||
// Try odd y coordinate
|
// If 32-byte x-only and even y failed, try odd y
|
||||||
pubKeyFull[0] = 0x03
|
if len(pub) == 32 {
|
||||||
if pubKeyInternal, err = s.ctx.ParsePublicKey(pubKeyFull); err != nil {
|
pubKeyFull[0] = 0x03
|
||||||
|
if pubKeyInternal, err = s.ctx.ParsePublicKey(pubKeyFull); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Compute ECDH - this returns the 32-byte x-coordinate of the shared point
|
// Compute ECDH - this returns the 32-byte x-coordinate of the shared point
|
||||||
if sharedX, err = s.ctx.ECDH(pubKeyInternal, s.secKey); err != nil {
|
if sharedX, err = s.ctx.ECDH(pubKeyInternal, s.secKey); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user