diff --git a/crypto/bridge.cpp b/crypto/bridge.cpp index 4c088a1..b115f19 100644 --- a/crypto/bridge.cpp +++ b/crypto/bridge.cpp @@ -39,4 +39,62 @@ int cn_check_key(const uint8_t pub[32]) { return crypto::check_key(pk) ? 0 : 1; } +// ── Key Derivation ──────────────────────────────────────── + +int cn_generate_key_derivation(const uint8_t pub[32], const uint8_t sec[32], + uint8_t derivation[32]) { + crypto::public_key pk; + crypto::secret_key sk; + crypto::key_derivation kd; + memcpy(&pk, pub, 32); + memcpy(&sk, sec, 32); + bool ok = crypto::generate_key_derivation(pk, sk, kd); + if (!ok) return 1; + memcpy(derivation, &kd, 32); + return 0; +} + +int cn_derive_public_key(const uint8_t derivation[32], uint64_t index, + const uint8_t base[32], uint8_t derived[32]) { + crypto::key_derivation kd; + crypto::public_key base_pk, derived_pk; + memcpy(&kd, derivation, 32); + memcpy(&base_pk, base, 32); + bool ok = crypto::derive_public_key(kd, index, base_pk, derived_pk); + if (!ok) return 1; + memcpy(derived, &derived_pk, 32); + return 0; +} + +int cn_derive_secret_key(const uint8_t derivation[32], uint64_t index, + const uint8_t base[32], uint8_t derived[32]) { + crypto::key_derivation kd; + crypto::secret_key base_sk, derived_sk; + memcpy(&kd, derivation, 32); + memcpy(&base_sk, base, 32); + crypto::derive_secret_key(kd, index, base_sk, derived_sk); + memcpy(derived, &derived_sk, 32); + return 0; +} + +// ── Key Images ──────────────────────────────────────────── + +int cn_generate_key_image(const uint8_t pub[32], const uint8_t sec[32], + uint8_t image[32]) { + crypto::public_key pk; + crypto::secret_key sk; + crypto::key_image ki; + memcpy(&pk, pub, 32); + memcpy(&sk, sec, 32); + crypto::generate_key_image(pk, sk, ki); + memcpy(image, &ki, 32); + return 0; +} + +int cn_validate_key_image(const uint8_t image[32]) { + crypto::key_image ki; + memcpy(&ki, image, 32); + return crypto::validate_key_image(ki) ? 0 : 1; +} + } // extern "C" diff --git a/crypto/bridge.h b/crypto/bridge.h index 3fe4780..556c16a 100644 --- a/crypto/bridge.h +++ b/crypto/bridge.h @@ -19,6 +19,19 @@ int cn_generate_keys(uint8_t pub[32], uint8_t sec[32]); int cn_secret_to_public(const uint8_t sec[32], uint8_t pub[32]); int cn_check_key(const uint8_t pub[32]); +// ── Key Derivation ──────────────────────────────────────── +int cn_generate_key_derivation(const uint8_t pub[32], const uint8_t sec[32], + uint8_t derivation[32]); +int cn_derive_public_key(const uint8_t derivation[32], uint64_t index, + const uint8_t base[32], uint8_t derived[32]); +int cn_derive_secret_key(const uint8_t derivation[32], uint64_t index, + const uint8_t base[32], uint8_t derived[32]); + +// ── Key Images ──────────────────────────────────────────── +int cn_generate_key_image(const uint8_t pub[32], const uint8_t sec[32], + uint8_t image[32]); +int cn_validate_key_image(const uint8_t image[32]); + #ifdef __cplusplus } #endif diff --git a/crypto/crypto_test.go b/crypto/crypto_test.go index ae2595a..ec9f744 100644 --- a/crypto/crypto_test.go +++ b/crypto/crypto_test.go @@ -66,3 +66,138 @@ func TestGenerateKeys_Good_Unique(t *testing.T) { t.Fatal("two GenerateKeys calls returned identical public keys") } } + +// ── Key Derivation ──────────────────────────────────────── + +func TestKeyDerivation_Good_Roundtrip(t *testing.T) { + // Alice and Bob generate key pairs; shared derivation must match. + pubA, secA, err := crypto.GenerateKeys() + if err != nil { + t.Fatalf("GenerateKeys (Alice): %v", err) + } + pubB, secB, err := crypto.GenerateKeys() + if err != nil { + t.Fatalf("GenerateKeys (Bob): %v", err) + } + + // D = secA * pubB must equal secB * pubA (ECDH commutativity). + dAB, err := crypto.GenerateKeyDerivation(pubB, secA) + if err != nil { + t.Fatalf("GenerateKeyDerivation(pubB, secA): %v", err) + } + dBA, err := crypto.GenerateKeyDerivation(pubA, secB) + if err != nil { + t.Fatalf("GenerateKeyDerivation(pubA, secB): %v", err) + } + if dAB != dBA { + t.Fatalf("ECDH mismatch:\n dAB: %x\n dBA: %x", dAB, dBA) + } +} + +func TestDerivePublicKey_Good_OutputScanning(t *testing.T) { + // Simulate one-time address generation and scanning. + // Sender knows (txPub, recipientPub). Recipient knows (txPub, recipientSec). + recipientPub, recipientSec, err := crypto.GenerateKeys() + if err != nil { + t.Fatalf("GenerateKeys (recipient): %v", err) + } + txPub, txSec, err := crypto.GenerateKeys() + if err != nil { + t.Fatalf("GenerateKeys (tx): %v", err) + } + + // Sender derives ephemeral output key. + derivSender, err := crypto.GenerateKeyDerivation(recipientPub, txSec) + if err != nil { + t.Fatalf("sender derivation: %v", err) + } + ephPub, err := crypto.DerivePublicKey(derivSender, 0, recipientPub) + if err != nil { + t.Fatalf("DerivePublicKey: %v", err) + } + if !crypto.CheckKey(ephPub) { + t.Fatal("ephemeral public key failed CheckKey") + } + + // Recipient re-derives and must get the same key. + derivRecipient, err := crypto.GenerateKeyDerivation(txPub, recipientSec) + if err != nil { + t.Fatalf("recipient derivation: %v", err) + } + ephPub2, err := crypto.DerivePublicKey(derivRecipient, 0, recipientPub) + if err != nil { + t.Fatalf("DerivePublicKey (recipient): %v", err) + } + if ephPub != ephPub2 { + t.Fatalf("output scanning mismatch:\n sender: %x\n recipient: %x", ephPub, ephPub2) + } + + // Recipient derives the secret key for spending. + ephSec, err := crypto.DeriveSecretKey(derivRecipient, 0, recipientSec) + if err != nil { + t.Fatalf("DeriveSecretKey: %v", err) + } + + // Verify: ephSec → ephPub must match. + ephPub3, err := crypto.SecretToPublic(ephSec) + if err != nil { + t.Fatalf("SecretToPublic(ephSec): %v", err) + } + if ephPub != ephPub3 { + t.Fatalf("ephemeral key pair mismatch:\n derived pub: %x\n sec→pub: %x", ephPub, ephPub3) + } +} + +// ── Key Images ──────────────────────────────────────────── + +func TestKeyImage_Good_Roundtrip(t *testing.T) { + pub, sec, err := crypto.GenerateKeys() + if err != nil { + t.Fatalf("GenerateKeys: %v", err) + } + + ki, err := crypto.GenerateKeyImage(pub, sec) + if err != nil { + t.Fatalf("GenerateKeyImage: %v", err) + } + + var zero [32]byte + if ki == zero { + t.Fatal("key image is zero") + } + + if !crypto.ValidateKeyImage(ki) { + t.Fatal("generated key image failed validation") + } +} + +func TestKeyImage_Good_Deterministic(t *testing.T) { + pub, sec, err := crypto.GenerateKeys() + if err != nil { + t.Fatalf("GenerateKeys: %v", err) + } + + ki1, err := crypto.GenerateKeyImage(pub, sec) + if err != nil { + t.Fatalf("GenerateKeyImage (1): %v", err) + } + ki2, err := crypto.GenerateKeyImage(pub, sec) + if err != nil { + t.Fatalf("GenerateKeyImage (2): %v", err) + } + + if ki1 != ki2 { + t.Fatalf("key image not deterministic:\n ki1: %x\n ki2: %x", ki1, ki2) + } +} + +func TestKeyImage_Bad_Invalid(t *testing.T) { + // 0xFF...FF is not a valid curve point, so not a valid key image. + bad := [32]byte{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF} + if crypto.ValidateKeyImage(bad) { + t.Fatal("0xFF...FF should fail ValidateKeyImage") + } +} diff --git a/crypto/keygen.go b/crypto/keygen.go index c503f9f..c0b1a42 100644 --- a/crypto/keygen.go +++ b/crypto/keygen.go @@ -41,3 +41,47 @@ func SecretToPublic(sec [32]byte) ([32]byte, error) { func CheckKey(pub [32]byte) bool { return C.cn_check_key((*C.uint8_t)(unsafe.Pointer(&pub[0]))) == 0 } + +// GenerateKeyDerivation computes the ECDH shared secret (key derivation). +func GenerateKeyDerivation(pub [32]byte, sec [32]byte) ([32]byte, error) { + var d [32]byte + rc := C.cn_generate_key_derivation( + (*C.uint8_t)(unsafe.Pointer(&pub[0])), + (*C.uint8_t)(unsafe.Pointer(&sec[0])), + (*C.uint8_t)(unsafe.Pointer(&d[0])), + ) + if rc != 0 { + return d, fmt.Errorf("crypto: generate_key_derivation failed") + } + return d, nil +} + +// DerivePublicKey derives an ephemeral public key for a transaction output. +func DerivePublicKey(derivation [32]byte, index uint64, base [32]byte) ([32]byte, error) { + var derived [32]byte + rc := C.cn_derive_public_key( + (*C.uint8_t)(unsafe.Pointer(&derivation[0])), + C.uint64_t(index), + (*C.uint8_t)(unsafe.Pointer(&base[0])), + (*C.uint8_t)(unsafe.Pointer(&derived[0])), + ) + if rc != 0 { + return derived, fmt.Errorf("crypto: derive_public_key failed") + } + return derived, nil +} + +// DeriveSecretKey derives the ephemeral secret key for a received output. +func DeriveSecretKey(derivation [32]byte, index uint64, base [32]byte) ([32]byte, error) { + var derived [32]byte + rc := C.cn_derive_secret_key( + (*C.uint8_t)(unsafe.Pointer(&derivation[0])), + C.uint64_t(index), + (*C.uint8_t)(unsafe.Pointer(&base[0])), + (*C.uint8_t)(unsafe.Pointer(&derived[0])), + ) + if rc != 0 { + return derived, fmt.Errorf("crypto: derive_secret_key failed") + } + return derived, nil +} diff --git a/crypto/keyimage.go b/crypto/keyimage.go new file mode 100644 index 0000000..c844886 --- /dev/null +++ b/crypto/keyimage.go @@ -0,0 +1,33 @@ +// SPDX-Licence-Identifier: EUPL-1.2 + +package crypto + +/* +#include "bridge.h" +*/ +import "C" + +import ( + "fmt" + "unsafe" +) + +// GenerateKeyImage computes the key image for a public/secret key pair. +// The key image is used for double-spend detection in ring signatures. +func GenerateKeyImage(pub [32]byte, sec [32]byte) ([32]byte, error) { + var ki [32]byte + rc := C.cn_generate_key_image( + (*C.uint8_t)(unsafe.Pointer(&pub[0])), + (*C.uint8_t)(unsafe.Pointer(&sec[0])), + (*C.uint8_t)(unsafe.Pointer(&ki[0])), + ) + if rc != 0 { + return ki, fmt.Errorf("crypto: generate_key_image failed") + } + return ki, nil +} + +// ValidateKeyImage checks that a key image is a valid curve point of the correct order. +func ValidateKeyImage(ki [32]byte) bool { + return C.cn_validate_key_image((*C.uint8_t)(unsafe.Pointer(&ki[0]))) == 0 +}