feat(consensus): wire up NLSAG ring signature verification
Connect crypto.CheckRingSignature() to verifyV1Signatures() so pre-HF4 transactions have their ring signatures cryptographically verified when a RingOutputsFn callback is provided. Co-Authored-By: Charon <charon@lethean.io>
This commit is contained in:
parent
ca3d8e04d1
commit
2c6211d78e
2 changed files with 138 additions and 1 deletions
|
|
@ -9,7 +9,9 @@ import (
|
|||
"fmt"
|
||||
|
||||
"forge.lthn.ai/core/go-blockchain/config"
|
||||
"forge.lthn.ai/core/go-blockchain/crypto"
|
||||
"forge.lthn.ai/core/go-blockchain/types"
|
||||
"forge.lthn.ai/core/go-blockchain/wire"
|
||||
)
|
||||
|
||||
// RingOutputsFn fetches the public keys for a ring at the given amount
|
||||
|
|
@ -60,7 +62,51 @@ func verifyV1Signatures(tx *types.Transaction, getRingOutputs RingOutputsFn) err
|
|||
return nil
|
||||
}
|
||||
|
||||
// TODO: Wire up crypto.CheckRingSignature() for each input.
|
||||
prefixHash := wire.TransactionPrefixHash(tx)
|
||||
|
||||
var sigIdx int
|
||||
for _, vin := range tx.Vin {
|
||||
inp, ok := vin.(types.TxInputToKey)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
// Extract absolute global indices from key offsets.
|
||||
offsets := make([]uint64, len(inp.KeyOffsets))
|
||||
for i, ref := range inp.KeyOffsets {
|
||||
offsets[i] = ref.GlobalIndex
|
||||
}
|
||||
|
||||
ringKeys, err := getRingOutputs(inp.Amount, offsets)
|
||||
if err != nil {
|
||||
return fmt.Errorf("consensus: failed to fetch ring outputs for input %d: %w",
|
||||
sigIdx, err)
|
||||
}
|
||||
|
||||
ringSigs := tx.Signatures[sigIdx]
|
||||
if len(ringSigs) != len(ringKeys) {
|
||||
return fmt.Errorf("consensus: input %d has %d signatures but ring size %d",
|
||||
sigIdx, len(ringSigs), len(ringKeys))
|
||||
}
|
||||
|
||||
// Convert typed slices to raw byte arrays for the crypto bridge.
|
||||
pubs := make([][32]byte, len(ringKeys))
|
||||
for i, pk := range ringKeys {
|
||||
pubs[i] = [32]byte(pk)
|
||||
}
|
||||
|
||||
sigs := make([][64]byte, len(ringSigs))
|
||||
for i, s := range ringSigs {
|
||||
sigs[i] = [64]byte(s)
|
||||
}
|
||||
|
||||
if !crypto.CheckRingSignature([32]byte(prefixHash), [32]byte(inp.KeyImage), pubs, sigs) {
|
||||
return fmt.Errorf("consensus: ring signature verification failed for input %d", sigIdx)
|
||||
}
|
||||
|
||||
sigIdx++
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
|
|||
91
consensus/verify_crypto_test.go
Normal file
91
consensus/verify_crypto_test.go
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
// 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 consensus
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"forge.lthn.ai/core/go-blockchain/config"
|
||||
"forge.lthn.ai/core/go-blockchain/crypto"
|
||||
"forge.lthn.ai/core/go-blockchain/types"
|
||||
"forge.lthn.ai/core/go-blockchain/wire"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestVerifyV1Signatures_Good_MockRing(t *testing.T) {
|
||||
pub, sec, err := crypto.GenerateKeys()
|
||||
require.NoError(t, err)
|
||||
|
||||
ki, err := crypto.GenerateKeyImage(pub, sec)
|
||||
require.NoError(t, err)
|
||||
|
||||
tx := &types.Transaction{
|
||||
Version: types.VersionPreHF4,
|
||||
Vin: []types.TxInput{
|
||||
types.TxInputToKey{
|
||||
Amount: 100,
|
||||
KeyOffsets: []types.TxOutRef{
|
||||
{Tag: types.RefTypeGlobalIndex, GlobalIndex: 0},
|
||||
},
|
||||
KeyImage: types.KeyImage(ki),
|
||||
},
|
||||
},
|
||||
Vout: []types.TxOutput{
|
||||
types.TxOutputBare{Amount: 90, Target: types.TxOutToKey{Key: types.PublicKey(pub)}},
|
||||
},
|
||||
}
|
||||
|
||||
prefixHash := wire.TransactionPrefixHash(tx)
|
||||
|
||||
sigs, err := crypto.GenerateRingSignature(
|
||||
[32]byte(prefixHash), ki, [][32]byte{pub}, sec, 0)
|
||||
require.NoError(t, err)
|
||||
|
||||
tx.Signatures = [][]types.Signature{make([]types.Signature, 1)}
|
||||
tx.Signatures[0][0] = types.Signature(sigs[0])
|
||||
|
||||
getRing := func(amount uint64, offsets []uint64) ([]types.PublicKey, error) {
|
||||
return []types.PublicKey{types.PublicKey(pub)}, nil
|
||||
}
|
||||
|
||||
err = VerifyTransactionSignatures(tx, config.MainnetForks, 100, getRing)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestVerifyV1Signatures_Bad_WrongSig(t *testing.T) {
|
||||
pub, sec, err := crypto.GenerateKeys()
|
||||
require.NoError(t, err)
|
||||
|
||||
ki, err := crypto.GenerateKeyImage(pub, sec)
|
||||
require.NoError(t, err)
|
||||
|
||||
tx := &types.Transaction{
|
||||
Version: types.VersionPreHF4,
|
||||
Vin: []types.TxInput{
|
||||
types.TxInputToKey{
|
||||
Amount: 100,
|
||||
KeyOffsets: []types.TxOutRef{
|
||||
{Tag: types.RefTypeGlobalIndex, GlobalIndex: 0},
|
||||
},
|
||||
KeyImage: types.KeyImage(ki),
|
||||
},
|
||||
},
|
||||
Vout: []types.TxOutput{
|
||||
types.TxOutputBare{Amount: 90, Target: types.TxOutToKey{Key: types.PublicKey(pub)}},
|
||||
},
|
||||
Signatures: [][]types.Signature{
|
||||
{types.Signature{}},
|
||||
},
|
||||
}
|
||||
|
||||
getRing := func(amount uint64, offsets []uint64) ([]types.PublicKey, error) {
|
||||
return []types.PublicKey{types.PublicKey(pub)}, nil
|
||||
}
|
||||
|
||||
err = VerifyTransactionSignatures(tx, config.MainnetForks, 100, getRing)
|
||||
assert.Error(t, err)
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue