feat(consensus): signature verification scaffold

VerifyTransactionSignatures with structural checks for v1 NLSAG and
v2+ CLSAG. Crypto bridge calls marked as TODO for wiring.

Co-Authored-By: Charon <charon@lethean.io>
This commit is contained in:
Claude 2026-02-21 01:04:59 +00:00
parent c2888c487b
commit 940155e822
No known key found for this signature in database
GPG key ID: AF404715446AEB41
2 changed files with 96 additions and 0 deletions

71
consensus/verify.go Normal file
View file

@ -0,0 +1,71 @@
// 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 (
"fmt"
"forge.lthn.ai/core/go-blockchain/config"
"forge.lthn.ai/core/go-blockchain/types"
)
// RingOutputsFn fetches the public keys for a ring at the given amount
// and offsets. Used to decouple consensus/ from chain storage.
type RingOutputsFn func(amount uint64, offsets []uint64) ([]types.PublicKey, error)
// VerifyTransactionSignatures verifies all ring signatures in a transaction.
// For coinbase transactions, this is a no-op (no signatures).
// For pre-HF4 transactions, NLSAG ring signatures are verified.
// For post-HF4, CLSAG signatures and proofs are verified.
//
// getRingOutputs may be nil for coinbase-only checks.
func VerifyTransactionSignatures(tx *types.Transaction, forks []config.HardFork,
height uint64, getRingOutputs RingOutputsFn) error {
// Coinbase: no signatures.
if isCoinbase(tx) {
return nil
}
hf4Active := config.IsHardForkActive(forks, config.HF4Zarcanum, height)
if !hf4Active {
return verifyV1Signatures(tx, getRingOutputs)
}
return verifyV2Signatures(tx, getRingOutputs)
}
// verifyV1Signatures checks NLSAG ring signatures for pre-HF4 transactions.
func verifyV1Signatures(tx *types.Transaction, getRingOutputs RingOutputsFn) error {
// Count key inputs.
var keyInputCount int
for _, vin := range tx.Vin {
if _, ok := vin.(types.TxInputToKey); ok {
keyInputCount++
}
}
if len(tx.Signatures) != keyInputCount {
return fmt.Errorf("consensus: signature count %d != input count %d",
len(tx.Signatures), keyInputCount)
}
// Actual NLSAG verification requires the crypto bridge and ring outputs.
// When getRingOutputs is nil, we can only check structural correctness.
if getRingOutputs == nil {
return nil
}
// TODO: Wire up crypto.CheckRingSignature() for each input.
return nil
}
// verifyV2Signatures checks CLSAG signatures and proofs for post-HF4 transactions.
func verifyV2Signatures(tx *types.Transaction, getRingOutputs RingOutputsFn) error {
// TODO: Wire up CLSAG verification and proof checks.
return nil
}

25
consensus/verify_test.go Normal file
View file

@ -0,0 +1,25 @@
//go:build !integration
package consensus
import (
"testing"
"forge.lthn.ai/core/go-blockchain/config"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestVerifyTransactionSignatures_Good_Coinbase(t *testing.T) {
// Coinbase transactions have no signatures to verify.
tx := validMinerTx(100)
err := VerifyTransactionSignatures(tx, config.MainnetForks, 100, nil)
require.NoError(t, err)
}
func TestVerifyTransactionSignatures_Bad_MissingSigs(t *testing.T) {
tx := validV1Tx()
tx.Signatures = nil // no signatures
err := VerifyTransactionSignatures(tx, config.MainnetForks, 100, nil)
assert.Error(t, err)
}