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 <charon@lethean.io>
This commit is contained in:
parent
9821ea4d21
commit
f022e61da9
4 changed files with 286 additions and 0 deletions
|
|
@ -5,6 +5,7 @@
|
|||
#include "bridge.h"
|
||||
|
||||
#include <cstring>
|
||||
#include <vector>
|
||||
#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<const crypto::public_key*> pk_ptrs(pubs_count);
|
||||
std::vector<crypto::public_key> 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<crypto::signature> 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<const crypto::public_key*> pk_ptrs(pubs_count);
|
||||
std::vector<crypto::public_key> 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<const crypto::signature*>(sigs);
|
||||
return crypto::check_ring_signature(h, ki, pk_ptrs.data(), pubs_count, sig_ptr) ? 0 : 1;
|
||||
}
|
||||
|
||||
} // extern "C"
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
99
crypto/signature.go
Normal file
99
crypto/signature.go
Normal file
|
|
@ -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
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue