feat(wallet): NLSAGSigner with ring signature interface
Signer interface abstracts signature generation for v1/v2+ extensibility. NLSAGSigner wraps the CGo ring signature functions for v0/v1 transactions. Co-Authored-By: Charon <charon@lethean.io>
This commit is contained in:
parent
3a5db81e13
commit
ff97d51550
2 changed files with 186 additions and 0 deletions
61
wallet/signer.go
Normal file
61
wallet/signer.go
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
// Copyright (c) 2017-2026 Lethean (https://lt.hn)
|
||||
//
|
||||
// Licensed under the European Union Public Licence (EUPL) version 1.2.
|
||||
// You may obtain a copy of the licence at:
|
||||
//
|
||||
// https://joinup.ec.europa.eu/software/page/eupl/licence-eupl
|
||||
//
|
||||
// SPDX-License-Identifier: EUPL-1.2
|
||||
|
||||
package wallet
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"forge.lthn.ai/core/go-blockchain/crypto"
|
||||
"forge.lthn.ai/core/go-blockchain/types"
|
||||
)
|
||||
|
||||
// Signer produces signatures for transaction inputs.
|
||||
type Signer interface {
|
||||
SignInput(prefixHash types.Hash, ephemeral KeyPair,
|
||||
ring []types.PublicKey, realIndex int) ([]types.Signature, error)
|
||||
Version() uint64
|
||||
}
|
||||
|
||||
// NLSAGSigner signs using NLSAG ring signatures (v0/v1 transactions).
|
||||
type NLSAGSigner struct{}
|
||||
|
||||
// SignInput generates a ring signature for a single transaction input. It
|
||||
// derives the key image from the ephemeral key pair, then produces one
|
||||
// signature element per ring member.
|
||||
func (s *NLSAGSigner) SignInput(prefixHash types.Hash, ephemeral KeyPair,
|
||||
ring []types.PublicKey, realIndex int) ([]types.Signature, error) {
|
||||
|
||||
ki, err := crypto.GenerateKeyImage(
|
||||
[32]byte(ephemeral.Public), [32]byte(ephemeral.Secret))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("wallet: key image: %w", err)
|
||||
}
|
||||
|
||||
pubs := make([][32]byte, len(ring))
|
||||
for i, k := range ring {
|
||||
pubs[i] = [32]byte(k)
|
||||
}
|
||||
|
||||
rawSigs, err := crypto.GenerateRingSignature(
|
||||
[32]byte(prefixHash), ki, pubs,
|
||||
[32]byte(ephemeral.Secret), realIndex)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("wallet: ring signature: %w", err)
|
||||
}
|
||||
|
||||
sigs := make([]types.Signature, len(rawSigs))
|
||||
for i, rs := range rawSigs {
|
||||
sigs[i] = types.Signature(rs)
|
||||
}
|
||||
return sigs, nil
|
||||
}
|
||||
|
||||
// Version returns the transaction version this signer targets.
|
||||
func (s *NLSAGSigner) Version() uint64 { return 1 }
|
||||
125
wallet/signer_test.go
Normal file
125
wallet/signer_test.go
Normal file
|
|
@ -0,0 +1,125 @@
|
|||
// Copyright (c) 2017-2026 Lethean (https://lt.hn)
|
||||
//
|
||||
// Licensed under the European Union Public Licence (EUPL) version 1.2.
|
||||
// SPDX-License-Identifier: EUPL-1.2
|
||||
|
||||
package wallet
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"forge.lthn.ai/core/go-blockchain/crypto"
|
||||
"forge.lthn.ai/core/go-blockchain/types"
|
||||
)
|
||||
|
||||
func TestNLSAGSignerRoundTrip(t *testing.T) {
|
||||
pub, sec, err := crypto.GenerateKeys()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
ki, err := crypto.GenerateKeyImage(pub, sec)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Build ring of 3 with our key at index 1.
|
||||
ring := make([]types.PublicKey, 3)
|
||||
for i := range ring {
|
||||
p, _, err := crypto.GenerateKeys()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
ring[i] = types.PublicKey(p)
|
||||
}
|
||||
ring[1] = types.PublicKey(pub)
|
||||
|
||||
var prefixHash types.Hash
|
||||
prefixHash[0] = 0xFF
|
||||
|
||||
signer := &NLSAGSigner{}
|
||||
sigs, err := signer.SignInput(prefixHash, KeyPair{
|
||||
Public: types.PublicKey(pub),
|
||||
Secret: types.SecretKey(sec),
|
||||
}, ring, 1)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(sigs) != 3 {
|
||||
t.Fatalf("got %d sigs, want 3", len(sigs))
|
||||
}
|
||||
|
||||
// Verify with crypto.CheckRingSignature.
|
||||
pubs := make([][32]byte, len(ring))
|
||||
for i, k := range ring {
|
||||
pubs[i] = [32]byte(k)
|
||||
}
|
||||
rawSigs := make([][64]byte, len(sigs))
|
||||
for i, s := range sigs {
|
||||
rawSigs[i] = [64]byte(s)
|
||||
}
|
||||
if !crypto.CheckRingSignature([32]byte(prefixHash), ki, pubs, rawSigs) {
|
||||
t.Fatal("ring signature verification failed")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNLSAGSignerVersion(t *testing.T) {
|
||||
signer := &NLSAGSigner{}
|
||||
if signer.Version() != 1 {
|
||||
t.Fatalf("version = %d, want 1", signer.Version())
|
||||
}
|
||||
}
|
||||
|
||||
func TestNLSAGSignerLargeRing(t *testing.T) {
|
||||
pub, sec, err := crypto.GenerateKeys()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
ki, err := crypto.GenerateKeyImage(pub, sec)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
ring := make([]types.PublicKey, 10)
|
||||
for i := range ring {
|
||||
p, _, err := crypto.GenerateKeys()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
ring[i] = types.PublicKey(p)
|
||||
}
|
||||
ring[5] = types.PublicKey(pub) // real at index 5
|
||||
|
||||
var prefixHash types.Hash
|
||||
for i := range prefixHash {
|
||||
prefixHash[i] = byte(i)
|
||||
}
|
||||
|
||||
signer := &NLSAGSigner{}
|
||||
sigs, err := signer.SignInput(prefixHash, KeyPair{
|
||||
Public: types.PublicKey(pub),
|
||||
Secret: types.SecretKey(sec),
|
||||
}, ring, 5)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(sigs) != 10 {
|
||||
t.Fatalf("got %d sigs, want 10", len(sigs))
|
||||
}
|
||||
|
||||
pubs := make([][32]byte, len(ring))
|
||||
for i, k := range ring {
|
||||
pubs[i] = [32]byte(k)
|
||||
}
|
||||
rawSigs := make([][64]byte, len(sigs))
|
||||
for i, s := range sigs {
|
||||
rawSigs[i] = [64]byte(s)
|
||||
}
|
||||
if !crypto.CheckRingSignature([32]byte(prefixHash), ki, pubs, rawSigs) {
|
||||
t.Fatal("large ring signature verification failed")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNLSAGSignerInterface(t *testing.T) {
|
||||
// Compile-time check that NLSAGSigner satisfies the Signer interface.
|
||||
var _ Signer = (*NLSAGSigner)(nil)
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue