Files
threshold/protocols/lss/adapters/full_coverage_test.go
zeekay 7ec0a63610 lss: gate research-preview Corona adapter behind -tags=researchpreview
Closes a HIGH-severity production-routing risk identified in the
pulsar/corona audit (2026-05-31).

The file protocols/lss/adapters/corona.go was a paper-grade
demonstrator that named itself "Corona" — colliding with the real
luxfi/corona Ring-LWE primitive. Anyone invoking the LSS factory with
`lss.NewLSS(lss.Corona, ...)` got the toy adapter, not the production
primitive. The toy adapter has:

 - A textbook-LWE "DKG" that sums each party's s_i into a combined
   master secret at the aggregator. NO Lagrange threshold sharing.
   The threshold parameter is decorative.

 - Q = 12289 (< 2^14) — far below any meaningful LWE security bound.

 - A Box-Muller-from-uniform-mod-Q Gaussian sampler using
   floating-point math on secret-dependent inputs. NOT constant-time.
   Side-channel leakage is GUARANTEED.

 - No matching verifier.

This commit:

  - adapters/corona.go     +build researchpreview
  - adapters/corona_test.go  +build researchpreview
  - adapters/corona_external_test.go  new gated external-test file
  - factory_corona_prod.go    new — production refuses with a routing
                              error pointing at
                              luxfi/threshold/protocols/corona →
                              luxfi/corona
  - factory_corona_research.go  new — research-preview returns the toy

Tests and benchmarks in adapters_test.go and full_coverage_test.go
that referenced NewCoronaAdapter / GetRecommendedParams have moved to
corona_external_test.go (gated). Production `go test ./protocols/lss/
adapters/...` passes; research `go test -tags researchpreview` also
passes.

Production users that need PQ threshold signing must route through
luxfi/threshold/protocols/corona (which wraps luxfi/corona): real
Ring-LWE with Pedersen-DKG, canonical wire codec (corona@42838bd), and
dudect-validated CT hot paths.

(cherry picked from commit 06e790c4a8c4a89a1b4a8aa2673fb02192dfb8c5)
2026-05-31 22:53:11 -07:00

603 lines
16 KiB
Go

// Package adapters - Comprehensive test coverage for all chain adapters
package adapters
import (
"crypto/rand"
"math/big"
"testing"
"github.com/luxfi/threshold/pkg/math/curve"
"github.com/luxfi/threshold/pkg/party"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
// TestAllChainsSupported verifies all chains have basic support
func TestAllChainsSupported(t *testing.T) {
chains := GetSupportedChains()
assert.GreaterOrEqual(t, len(chains), 12, "should support at least 12 chains")
// Verify major chains are included
expectedChains := []string{
"xrpl", "ethereum", "bitcoin", "solana", "cardano",
"cosmos", "polkadot", "lux", "binance",
// Note: TON adapter is included in adapters package
}
for _, expected := range expectedChains {
assert.Contains(t, chains, expected, "should support %s", expected)
}
}
// TestXRPLAdapter tests XRPL-specific features
func TestXRPLAdapter(t *testing.T) {
t.Run("ECDSA", func(t *testing.T) {
adapter, err := NewXRPLAdapter(SignatureECDSA, false)
require.NoError(t, err)
require.NotNil(t, adapter)
// Test digest computation with STX prefix
txBlob := make([]byte, 100)
rand.Read(txBlob)
digest, err := adapter.Digest(txBlob)
require.NoError(t, err)
assert.Len(t, digest, 32, "SHA-512Half should be 32 bytes")
// Test multi-signing with SMT prefix
multiAdapter, err := NewXRPLAdapter(SignatureECDSA, true)
require.NoError(t, err)
multiDigest, err := multiAdapter.Digest(txBlob)
require.NoError(t, err)
assert.NotEqual(t, digest, multiDigest, "STX and SMT should produce different digests")
})
t.Run("EdDSA", func(t *testing.T) {
adapter, err := NewXRPLAdapter(SignatureEdDSA, false)
require.NoError(t, err)
require.NotNil(t, adapter)
// Test Ed25519 public key formatting
pubKey := curve.Secp256k1{}.NewBasePoint() // Placeholder
formatted := adapter.FormatPublicKey(pubKey)
assert.NotEmpty(t, formatted)
})
t.Run("ValidateConfig", func(t *testing.T) {
adapter, err := NewXRPLAdapter(SignatureECDSA, false)
require.NoError(t, err)
config := &UnifiedConfig{
SignatureScheme: SignatureECDSA,
Threshold: 3,
PartyIDs: []party.ID{"alice", "bob", "charlie"},
VerificationShares: map[party.ID]interface{}{
"alice": curve.Secp256k1{}.NewBasePoint(),
"bob": curve.Secp256k1{}.NewBasePoint(),
"charlie": curve.Secp256k1{}.NewBasePoint(),
},
}
err = adapter.ValidateConfig(config)
assert.NoError(t, err)
// Test invalid threshold
config.Threshold = 10
err = adapter.ValidateConfig(config)
assert.Error(t, err, "should reject threshold > 8")
})
}
// TestEthereumAdapter tests Ethereum-specific features
func TestEthereumAdapter(t *testing.T) {
adapter := NewEthereumAdapter()
require.NotNil(t, adapter)
t.Run("LegacyTransaction", func(t *testing.T) {
tx := &LegacyTransaction{
Nonce: 1,
GasPrice: big.NewInt(20000000000),
GasLimit: 21000,
Value: big.NewInt(1000000000000000000),
}
digest, err := adapter.Digest(tx)
require.NoError(t, err)
assert.Len(t, digest, 32, "Keccak256 should be 32 bytes")
})
t.Run("EIP1559Transaction", func(t *testing.T) {
tx := &EIP1559Transaction{
ChainID: big.NewInt(1),
Nonce: 1,
MaxPriorityFeePerGas: big.NewInt(2000000000),
MaxFeePerGas: big.NewInt(30000000000),
GasLimit: 21000,
Value: big.NewInt(1000000000000000000),
}
digest, err := adapter.Digest(tx)
require.NoError(t, err)
assert.Len(t, digest, 32)
})
t.Run("MessageHash", func(t *testing.T) {
message := []byte("Hello Ethereum!")
digest, err := adapter.Digest(message)
require.NoError(t, err)
assert.Len(t, digest, 32)
})
t.Run("GasEstimation", func(t *testing.T) {
tx := &LegacyTransaction{}
gas, err := adapter.EstimateGas(tx)
require.NoError(t, err)
assert.GreaterOrEqual(t, gas, uint64(21000))
})
}
// TestBitcoinAdapter tests Bitcoin-specific features
func TestBitcoinAdapter(t *testing.T) {
t.Run("ECDSA", func(t *testing.T) {
adapter := NewBitcoinAdapter(SignatureECDSA)
require.NotNil(t, adapter)
// Create a mock Bitcoin transaction for testing
tx := &LegacyBitcoinTx{
Version: 1,
Inputs: []Input{},
Outputs: []Output{},
LockTime: 0,
SigHash: SigHashAll,
}
digest, err := adapter.Digest(tx)
require.NoError(t, err)
assert.Len(t, digest, 32)
})
t.Run("Schnorr", func(t *testing.T) {
adapter := NewBitcoinAdapter(SignatureSchnorr)
require.NotNil(t, adapter)
// Test Taproot transaction
// Create a mock Taproot transaction for testing
tx := &TaprootTx{
SegwitTx: SegwitTx{
Version: 2,
InputTxID: [32]byte{},
InputIndex: 0,
ScriptCode: []byte{},
Amount: 0,
},
ScriptPath: false,
}
digest, err := adapter.Digest(tx)
require.NoError(t, err)
assert.Len(t, digest, 32)
})
t.Run("SigHashTypes", func(t *testing.T) {
adapter := NewBitcoinAdapter(SignatureECDSA)
// Test basic digest computation with proper transaction type
tx := &LegacyBitcoinTx{
Version: 1,
Inputs: []Input{},
Outputs: []Output{},
LockTime: 0,
SigHash: SigHashAll,
}
digest, err := adapter.Digest(tx)
require.NoError(t, err)
assert.Len(t, digest, 32)
})
}
// TestSolanaAdapter tests Solana-specific features
func TestSolanaAdapter(t *testing.T) {
adapter := NewSolanaAdapter()
require.NotNil(t, adapter)
t.Run("Transaction", func(t *testing.T) {
msg := &SolanaMessage{
NumRequiredSignatures: 1,
NumReadonlySignedAccounts: 0,
NumReadonlyUnsignedAccounts: 1,
AccountKeys: make([][32]byte, 3),
RecentBlockhash: [32]byte{},
}
tx := &SolanaTransaction{
Message: msg,
}
digest, err := adapter.Digest(tx)
require.NoError(t, err)
assert.NotNil(t, digest)
})
t.Run("TransferInstruction", func(t *testing.T) {
adapter := NewSolanaAdapter()
var from, to [32]byte
rand.Read(from[:])
rand.Read(to[:])
instruction := adapter.CreateTransferInstruction(from, to, 1000000000)
assert.NotNil(t, instruction)
assert.Equal(t, byte(0), instruction.ProgramIDIndex)
})
t.Run("ProgramDerivedAddress", func(t *testing.T) {
adapter := NewSolanaAdapter()
var programID [32]byte
rand.Read(programID[:])
seeds := [][]byte{
[]byte("threshold"),
[]byte("wallet"),
}
pda, bump, err := adapter.ComputeProgramDerivedAddress(programID, seeds)
require.NoError(t, err)
assert.NotEqual(t, [32]byte{}, pda)
assert.Greater(t, bump, byte(0))
})
}
// TestTONAdapter tests TON-specific features
func TestTONAdapter(t *testing.T) {
t.Run("Basechain", func(t *testing.T) {
adapter := NewTONAdapter(0) // basechain
require.NotNil(t, adapter)
msg := &TONMessage{
Info: TONMessageInfo{
IHRDisabled: true,
Bounce: true,
Value: TONCurrencyCollection{Grams: 1000000000},
},
Body: []byte("test message"),
}
digest, err := adapter.Digest(msg)
require.NoError(t, err)
assert.Len(t, digest, 32, "SHA-256 should be 32 bytes")
})
t.Run("Masterchain", func(t *testing.T) {
adapter := NewTONAdapter(-1) // masterchain
require.NotNil(t, adapter)
tx := &TONTransaction{
Account: TONAddress{
Workchain: -1,
Hash: [32]byte{},
},
Lt: 1000000,
Now: 1234567890,
}
digest, err := adapter.Digest(tx)
require.NoError(t, err)
assert.Len(t, digest, 32)
})
t.Run("AddressGeneration", func(t *testing.T) {
adapter := NewTONAdapter(0)
var pubKey [32]byte
rand.Read(pubKey[:])
addr := adapter.GenerateTONAddress(pubKey)
assert.Equal(t, int32(0), addr.Workchain)
assert.NotEqual(t, [32]byte{}, addr.Hash)
})
t.Run("GasEstimation", func(t *testing.T) {
adapter := NewTONAdapter(0)
msg := &TONMessage{
Body: make([]byte, 1000),
}
gas := adapter.EstimateGas(msg)
assert.Greater(t, gas, uint64(10000))
})
}
// TestCardanoAdapter tests Cardano-specific features
func TestCardanoAdapter(t *testing.T) {
t.Run("Ed25519Native", func(t *testing.T) {
adapter, err := NewCardanoAdapter(SignatureEdDSA, 0x01, EraBabbage)
require.NoError(t, err)
require.NotNil(t, adapter)
tx := &CardanoTransaction{
Body: &TransactionBody{
Inputs: []TransactionInput{
{TxID: [32]byte{}, Index: 0},
},
Outputs: []TransactionOutput{
{Value: Value{Coin: 1000000}},
},
Fee: 200000,
TTL: 1000000,
},
IsValid: true,
}
digest, err := adapter.Digest(tx)
require.NoError(t, err)
assert.Len(t, digest, 32, "Blake2b-256 should be 32 bytes")
})
t.Run("ECDSAInterop", func(t *testing.T) {
adapter, err := NewCardanoAdapter(SignatureECDSA, 0x01, EraBabbage)
require.NoError(t, err)
require.NotNil(t, adapter)
config := &UnifiedConfig{
SignatureScheme: SignatureECDSA,
Group: curve.Secp256k1{},
}
err = adapter.ValidateConfig(config)
assert.NoError(t, err)
})
t.Run("SchnorrInterop", func(t *testing.T) {
adapter, err := NewCardanoAdapter(SignatureSchnorr, 0x01, EraBabbage)
require.NoError(t, err)
require.NotNil(t, adapter)
// Test Schnorr signature aggregation
parts := []PartialSig{
&SchnorrPartialSig{
PartyID: "alice",
S: curve.Secp256k1{}.NewScalar(),
},
}
full, err := adapter.AggregateEC(parts)
require.NoError(t, err)
assert.NotNil(t, full)
})
t.Run("AddressGeneration", func(t *testing.T) {
adapter, err := NewCardanoAdapter(SignatureEdDSA, 0x01, EraBabbage)
require.NoError(t, err)
var paymentKey, stakeKey [32]byte
rand.Read(paymentKey[:])
rand.Read(stakeKey[:])
addr := adapter.GenerateCardanoAddress(paymentKey, stakeKey)
assert.Equal(t, BaseAddress, addr.Type)
assert.Equal(t, byte(0x01), addr.Network)
})
t.Run("FeeEstimation", func(t *testing.T) {
adapter, err := NewCardanoAdapter(SignatureEdDSA, 0x01, EraBabbage)
require.NoError(t, err)
tx := &CardanoTransaction{
Body: &TransactionBody{
Inputs: make([]TransactionInput, 2),
Outputs: make([]TransactionOutput, 2),
Fee: 0,
},
}
fee := adapter.EstimateFee(tx)
assert.Greater(t, fee, uint64(155381), "minimum fee should be applied")
})
}
// TestCoronaAdapter (research preview only) lives in corona_test.go
// behind -tags=researchpreview, mirroring the LSS-side adapter gate.
// Production builds: route post-quantum threshold through
// luxfi/threshold/protocols/corona → luxfi/corona instead. See the
// disclosure block at the top of adapters/corona.go.
// TestChainRequirements verifies chain-specific requirements
func TestChainRequirements(t *testing.T) {
t.Run("XRPL", func(t *testing.T) {
req := GetChainRequirements("xrpl")
require.NotNil(t, req)
assert.True(t, req["low_s_required"].(bool))
assert.True(t, req["prefix_ed25519"].(bool))
assert.Equal(t, 8, req["max_threshold"])
})
t.Run("Ethereum", func(t *testing.T) {
req := GetChainRequirements("ethereum")
require.NotNil(t, req)
assert.True(t, req["low_s_required"].(bool))
assert.True(t, req["eip155_chainid"].(bool))
assert.True(t, req["typed_tx"].(bool))
})
t.Run("Bitcoin", func(t *testing.T) {
req := GetChainRequirements("bitcoin")
require.NotNil(t, req)
assert.True(t, req["low_s_required"].(bool))
assert.True(t, req["taproot_support"].(bool))
assert.True(t, req["sighash_types"].(bool))
})
t.Run("Solana", func(t *testing.T) {
req := GetChainRequirements("solana")
require.NotNil(t, req)
assert.True(t, req["ed25519_only"].(bool))
assert.True(t, req["program_verify"].(bool))
})
t.Run("Polkadot", func(t *testing.T) {
req := GetChainRequirements("polkadot")
require.NotNil(t, req)
assert.True(t, req["sr25519_native"].(bool))
assert.True(t, req["merlin_transcript"].(bool))
})
}
// TestAdapterFactory tests the factory pattern
func TestAdapterFactory(t *testing.T) {
factory := &AdapterFactory{}
testCases := []struct {
chain string
sigType SignatureType
shouldBeNil bool
}{
{"xrpl", SignatureECDSA, false},
{"xrpl", SignatureEdDSA, false},
{"ethereum", SignatureECDSA, false},
{"bitcoin", SignatureECDSA, false},
{"bitcoin", SignatureSchnorr, false},
{"solana", SignatureEdDSA, false},
{"ton", SignatureEdDSA, false},
{"cardano", SignatureEdDSA, false},
{"cardano", SignatureECDSA, false},
{"cardano", SignatureSchnorr, false},
{"cosmos", SignatureECDSA, true}, // Not implemented yet
{"polkadot", SignatureSchnorr, true}, // Not implemented yet
{"unknown", SignatureECDSA, true},
}
for _, tc := range testCases {
adapter := factory.NewAdapter(tc.chain, tc.sigType)
if tc.shouldBeNil {
assert.Nil(t, adapter, "%s should not have adapter", tc.chain)
} else {
assert.NotNil(t, adapter, "%s should have adapter", tc.chain)
}
}
}
// TestSignatureTypes tests all signature type implementations
func TestSignatureTypes(t *testing.T) {
group := curve.Secp256k1{}
t.Run("ECDSAPartialSig", func(t *testing.T) {
sig := &ECDSAPartialSig{
PartyID: "alice",
R: group.NewScalar(),
S: group.NewScalar(),
}
assert.Equal(t, party.ID("alice"), sig.GetPartyID())
serialized := sig.Serialize()
assert.NotEmpty(t, serialized)
})
t.Run("EdDSAPartialSig", func(t *testing.T) {
sig := &EdDSAPartialSig{
PartyID: "bob",
R: group.NewBasePoint(),
Z: group.NewScalar(),
}
assert.Equal(t, party.ID("bob"), sig.GetPartyID())
serialized := sig.Serialize()
assert.NotEmpty(t, serialized)
})
t.Run("CoronaPartialSig", func(t *testing.T) {
sig := &CoronaPartialSig{
PartyID: "charlie",
Share: []byte("lattice_element"),
}
assert.Equal(t, party.ID("charlie"), sig.GetPartyID())
serialized := sig.Serialize()
assert.NotNil(t, serialized)
})
}
// TestCrossChainCompatibility tests cross-chain signature compatibility
func TestCrossChainCompatibility(t *testing.T) {
// Test that signatures from one chain can be verified on another
// when using compatible signature schemes
message := []byte("cross-chain message")
t.Run("ECDSA_Compatibility", func(t *testing.T) {
chains := []string{"ethereum", "bitcoin", "xrpl"}
for _, chain := range chains {
factory := &AdapterFactory{}
adapter := factory.NewAdapter(chain, SignatureECDSA)
if adapter == nil {
continue
}
digest, err := adapter.Digest(message)
require.NoError(t, err)
assert.Len(t, digest, 32, "%s should produce 32-byte digest", chain)
}
})
t.Run("EdDSA_Compatibility", func(t *testing.T) {
chains := []string{"solana", "ton", "cardano"}
for _, chain := range chains {
factory := &AdapterFactory{}
adapter := factory.NewAdapter(chain, SignatureEdDSA)
if adapter == nil {
continue
}
digest, err := adapter.Digest(message)
require.NoError(t, err)
assert.NotNil(t, digest, "%s should produce valid digest", chain)
}
})
}
// BenchmarkAdapters benchmarks adapter performance
func BenchmarkAdapters(b *testing.B) {
message := make([]byte, 1000)
rand.Read(message)
b.Run("XRPL_Digest", func(b *testing.B) {
adapter, err := NewXRPLAdapter(SignatureECDSA, false)
if err != nil {
b.Fatal(err)
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, _ = adapter.Digest(message)
}
})
b.Run("Ethereum_Digest", func(b *testing.B) {
adapter := NewEthereumAdapter()
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, _ = adapter.Digest(message)
}
})
b.Run("TON_Digest", func(b *testing.B) {
adapter := NewTONAdapter(0)
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, _ = adapter.Digest(message)
}
})
b.Run("Cardano_Digest", func(b *testing.B) {
adapter, err := NewCardanoAdapter(SignatureEdDSA, 0x01, EraBabbage)
if err != nil {
b.Fatal(err)
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, _ = adapter.Digest(message)
}
})
}