From f022e61da99b41003b54f099b6a30349543a47e9 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 20 Feb 2026 18:30:53 +0000 Subject: [PATCH] feat(crypto): standard and ring signature (NLSAG) generation/verification Standard signature sign/verify round-trip with negative tests (wrong key, wrong message). Ring signature (NLSAG) uses flat buffer C API to avoid double-pointer indirection across CGo boundary. Round-trip with 4-member ring and wrong-message negative test. All 15 tests pass with race detector. Co-Authored-By: Charon --- crypto/bridge.cpp | 75 ++++++++++++++++++++++++++++++++ crypto/bridge.h | 15 +++++++ crypto/crypto_test.go | 97 ++++++++++++++++++++++++++++++++++++++++++ crypto/signature.go | 99 +++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 286 insertions(+) create mode 100644 crypto/signature.go diff --git a/crypto/bridge.cpp b/crypto/bridge.cpp index b115f19..74e999c 100644 --- a/crypto/bridge.cpp +++ b/crypto/bridge.cpp @@ -5,6 +5,7 @@ #include "bridge.h" #include +#include #include "crypto.h" #include "hash-ops.h" @@ -97,4 +98,78 @@ int cn_validate_key_image(const uint8_t image[32]) { return crypto::validate_key_image(ki) ? 0 : 1; } +// ── Standard Signatures ────────────────────────────────── + +int cn_generate_signature(const uint8_t hash[32], const uint8_t pub[32], + const uint8_t sec[32], uint8_t sig[64]) { + crypto::hash h; + crypto::public_key pk; + crypto::secret_key sk; + crypto::signature s; + memcpy(&h, hash, 32); + memcpy(&pk, pub, 32); + memcpy(&sk, sec, 32); + crypto::generate_signature(h, pk, sk, s); + memcpy(sig, &s, 64); + return 0; +} + +int cn_check_signature(const uint8_t hash[32], const uint8_t pub[32], + const uint8_t sig[64]) { + crypto::hash h; + crypto::public_key pk; + crypto::signature s; + memcpy(&h, hash, 32); + memcpy(&pk, pub, 32); + memcpy(&s, sig, 64); + return crypto::check_signature(h, pk, s) ? 0 : 1; +} + +// ── Ring Signatures (NLSAG) ───────────────────────────── + +int cn_generate_ring_signature(const uint8_t hash[32], const uint8_t image[32], + const uint8_t *pubs, size_t pubs_count, + const uint8_t sec[32], size_t sec_index, + uint8_t *sigs) { + crypto::hash h; + crypto::key_image ki; + crypto::secret_key sk; + memcpy(&h, hash, 32); + memcpy(&ki, image, 32); + memcpy(&sk, sec, 32); + + // Reconstruct pointer array from flat buffer. + std::vector pk_ptrs(pubs_count); + std::vector pk_storage(pubs_count); + for (size_t i = 0; i < pubs_count; i++) { + memcpy(&pk_storage[i], pubs + i * 32, 32); + pk_ptrs[i] = &pk_storage[i]; + } + + std::vector sig_vec(pubs_count); + crypto::generate_ring_signature(h, ki, pk_ptrs.data(), pubs_count, + sk, sec_index, sig_vec.data()); + memcpy(sigs, sig_vec.data(), pubs_count * 64); + return 0; +} + +int cn_check_ring_signature(const uint8_t hash[32], const uint8_t image[32], + const uint8_t *pubs, size_t pubs_count, + const uint8_t *sigs) { + crypto::hash h; + crypto::key_image ki; + memcpy(&h, hash, 32); + memcpy(&ki, image, 32); + + std::vector pk_ptrs(pubs_count); + std::vector pk_storage(pubs_count); + for (size_t i = 0; i < pubs_count; i++) { + memcpy(&pk_storage[i], pubs + i * 32, 32); + pk_ptrs[i] = &pk_storage[i]; + } + + auto* sig_ptr = reinterpret_cast(sigs); + return crypto::check_ring_signature(h, ki, pk_ptrs.data(), pubs_count, sig_ptr) ? 0 : 1; +} + } // extern "C" diff --git a/crypto/bridge.h b/crypto/bridge.h index 556c16a..458e6ed 100644 --- a/crypto/bridge.h +++ b/crypto/bridge.h @@ -32,6 +32,21 @@ 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]); +// ── Standard Signatures ─────────────────────────────────── +int cn_generate_signature(const uint8_t hash[32], const uint8_t pub[32], + const uint8_t sec[32], uint8_t sig[64]); +int cn_check_signature(const uint8_t hash[32], const uint8_t pub[32], + const uint8_t sig[64]); + +// ── Ring Signatures (NLSAG) ────────────────────────────── +int cn_generate_ring_signature(const uint8_t hash[32], const uint8_t image[32], + const uint8_t *pubs, size_t pubs_count, + const uint8_t sec[32], size_t sec_index, + uint8_t *sigs); +int cn_check_ring_signature(const uint8_t hash[32], const uint8_t image[32], + const uint8_t *pubs, size_t pubs_count, + const uint8_t *sigs); + #ifdef __cplusplus } #endif diff --git a/crypto/crypto_test.go b/crypto/crypto_test.go index ec9f744..e4edf68 100644 --- a/crypto/crypto_test.go +++ b/crypto/crypto_test.go @@ -201,3 +201,100 @@ func TestKeyImage_Bad_Invalid(t *testing.T) { t.Fatal("0xFF...FF should fail ValidateKeyImage") } } + +// ── Standard Signatures ────────────────────────────────── + +func TestSignature_Good_Roundtrip(t *testing.T) { + pub, sec, _ := crypto.GenerateKeys() + + // Sign a message hash. + msg := crypto.FastHash([]byte("test message")) + sig, err := crypto.GenerateSignature(msg, pub, sec) + if err != nil { + t.Fatalf("GenerateSignature: %v", err) + } + + // Verify with correct key. + if !crypto.CheckSignature(msg, pub, sig) { + t.Fatal("valid signature failed verification") + } +} + +func TestSignature_Bad_WrongKey(t *testing.T) { + pub, sec, _ := crypto.GenerateKeys() + pub2, _, _ := crypto.GenerateKeys() + + msg := crypto.FastHash([]byte("test")) + sig, _ := crypto.GenerateSignature(msg, pub, sec) + + // Verify with wrong public key should fail. + if crypto.CheckSignature(msg, pub2, sig) { + t.Fatal("signature verified with wrong public key") + } +} + +func TestSignature_Bad_WrongMessage(t *testing.T) { + pub, sec, _ := crypto.GenerateKeys() + + msg1 := crypto.FastHash([]byte("message 1")) + msg2 := crypto.FastHash([]byte("message 2")) + sig, _ := crypto.GenerateSignature(msg1, pub, sec) + + if crypto.CheckSignature(msg2, pub, sig) { + t.Fatal("signature verified with wrong message") + } +} + +// ── Ring Signatures (NLSAG) ───────────────────────────── + +func TestRingSignature_Good_Roundtrip(t *testing.T) { + // Create a ring of 4 public keys. The real signer is at index 1. + ringSize := 4 + realIndex := 1 + + pubs := make([][32]byte, ringSize) + var realSec [32]byte + for i := 0; i < ringSize; i++ { + pub, sec, _ := crypto.GenerateKeys() + pubs[i] = pub + if i == realIndex { + realSec = sec + } + } + + // Generate key image for the real key. + ki, _ := crypto.GenerateKeyImage(pubs[realIndex], realSec) + + // Sign. + msg := crypto.FastHash([]byte("ring sig test")) + sigs, err := crypto.GenerateRingSignature(msg, ki, pubs, realSec, realIndex) + if err != nil { + t.Fatalf("GenerateRingSignature: %v", err) + } + + // Verify. + if !crypto.CheckRingSignature(msg, ki, pubs, sigs) { + t.Fatal("valid ring signature failed verification") + } +} + +func TestRingSignature_Bad_WrongMessage(t *testing.T) { + pubs := make([][32]byte, 3) + var sec [32]byte + for i := range pubs { + pub, s, _ := crypto.GenerateKeys() + pubs[i] = pub + if i == 0 { + sec = s + } + } + ki, _ := crypto.GenerateKeyImage(pubs[0], sec) + + msg1 := crypto.FastHash([]byte("msg1")) + msg2 := crypto.FastHash([]byte("msg2")) + sigs, _ := crypto.GenerateRingSignature(msg1, ki, pubs, sec, 0) + + if crypto.CheckRingSignature(msg2, ki, pubs, sigs) { + t.Fatal("ring signature verified with wrong message") + } +} diff --git a/crypto/signature.go b/crypto/signature.go new file mode 100644 index 0000000..6d6cb56 --- /dev/null +++ b/crypto/signature.go @@ -0,0 +1,99 @@ +// SPDX-Licence-Identifier: EUPL-1.2 + +package crypto + +/* +#include "bridge.h" +*/ +import "C" + +import ( + "fmt" + "unsafe" +) + +// GenerateSignature creates a standard (non-ring) signature. +func GenerateSignature(hash [32]byte, pub [32]byte, sec [32]byte) ([64]byte, error) { + var sig [64]byte + rc := C.cn_generate_signature( + (*C.uint8_t)(unsafe.Pointer(&hash[0])), + (*C.uint8_t)(unsafe.Pointer(&pub[0])), + (*C.uint8_t)(unsafe.Pointer(&sec[0])), + (*C.uint8_t)(unsafe.Pointer(&sig[0])), + ) + if rc != 0 { + return sig, fmt.Errorf("crypto: generate_signature failed") + } + return sig, nil +} + +// CheckSignature verifies a standard signature. +func CheckSignature(hash [32]byte, pub [32]byte, sig [64]byte) bool { + return C.cn_check_signature( + (*C.uint8_t)(unsafe.Pointer(&hash[0])), + (*C.uint8_t)(unsafe.Pointer(&pub[0])), + (*C.uint8_t)(unsafe.Pointer(&sig[0])), + ) == 0 +} + +// GenerateRingSignature creates a ring signature using the given key ring. +// pubs contains the public keys of all ring members. +// sec is the secret key of the actual signer at position secIndex. +// Returns one signature pair per ring member. +func GenerateRingSignature(hash [32]byte, image [32]byte, pubs [][32]byte, + sec [32]byte, secIndex int) ([][64]byte, error) { + + n := len(pubs) + flatPubs := make([]byte, n*32) + for i, p := range pubs { + copy(flatPubs[i*32:], p[:]) + } + + flatSigs := make([]byte, n*64) + rc := C.cn_generate_ring_signature( + (*C.uint8_t)(unsafe.Pointer(&hash[0])), + (*C.uint8_t)(unsafe.Pointer(&image[0])), + (*C.uint8_t)(unsafe.Pointer(&flatPubs[0])), + C.size_t(n), + (*C.uint8_t)(unsafe.Pointer(&sec[0])), + C.size_t(secIndex), + (*C.uint8_t)(unsafe.Pointer(&flatSigs[0])), + ) + if rc != 0 { + return nil, fmt.Errorf("crypto: generate_ring_signature failed") + } + + sigs := make([][64]byte, n) + for i := range sigs { + copy(sigs[i][:], flatSigs[i*64:]) + } + return sigs, nil +} + +// CheckRingSignature verifies a ring signature. +func CheckRingSignature(hash [32]byte, image [32]byte, pubs [][32]byte, + sigs [][64]byte) bool { + + n := len(pubs) + if len(sigs) != n { + return false + } + + flatPubs := make([]byte, n*32) + for i, p := range pubs { + copy(flatPubs[i*32:], p[:]) + } + + flatSigs := make([]byte, n*64) + for i, s := range sigs { + copy(flatSigs[i*64:], s[:]) + } + + return C.cn_check_ring_signature( + (*C.uint8_t)(unsafe.Pointer(&hash[0])), + (*C.uint8_t)(unsafe.Pointer(&image[0])), + (*C.uint8_t)(unsafe.Pointer(&flatPubs[0])), + C.size_t(n), + (*C.uint8_t)(unsafe.Pointer(&flatSigs[0])), + ) == 0 +}