mirror of
https://github.com/luxfi/node.git
synced 2026-07-27 03:39:39 +00:00
node/consensus/quasar + node/consensus/zap + node/node/quasar.go were a
superseded duplicate of the live post-quantum finality path
(consensus/protocol/quasar WeightedQuorumCert, driven by the chain engine).
The node-side hub wrapped that live core but added only dormant scaffolding:
- an undriven P-Chain finality loop (finCh never fed; run() sat idle)
- a fail-closed CoronaCoordinator stub (Sign/Verify errored in prod,
InitializeCorona was test-only, so q.corona stayed nil)
- a QuantumFinality store nothing read (only n.Quasar.Stop() was ever called)
Dependency trace: zero cross-repo importers; the Q-Chain VM
(luxfi/chains/quantumvm) is external, independent, and finalized by the live
chain-engine QuorumCert path, not this hub; consensus/zap (ZAP/DID agentic
bridge) was a collateral dormant island on the same stub with no external
importers. Wiring was impossible without driving the live accept path or
adding a redundant second finality authority, both out of scope, so the hub
is removed rather than wired.
Live consensus/protocol/quasar and consensus/engine/chain are untouched.
Q-Chain VM registration, genesis, and critical-by-default status are
unchanged. Genuine PQ tests (TLS hybrid KEX, ML-DSA UTXO, hostname
addressing) are preserved; only the dormant-hub tests were removed.
634 lines
18 KiB
Go
634 lines
18 KiB
Go
// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved.
|
|
// See the file LICENSE for licensing terms.
|
|
|
|
// Package pq provides P+Q (Post-Quantum) integration tests for Lux network.
|
|
//
|
|
// These tests verify that all post-quantum security measures are enforced:
|
|
// - TLS connections use X25519MLKEM768 hybrid key exchange (no fallback)
|
|
// - SignedHost uses DNS hostnames only (no IP literals)
|
|
// - ML-DSA signatures work for X-Chain UTXOs
|
|
//
|
|
// Run: go test -v ./tests/pq/...
|
|
package pq
|
|
|
|
import (
|
|
"bytes"
|
|
"crypto/ecdsa"
|
|
"crypto/elliptic"
|
|
"crypto/rand"
|
|
"crypto/tls"
|
|
"crypto/x509"
|
|
"crypto/x509/pkix"
|
|
"errors"
|
|
"github.com/go-json-experiment/json"
|
|
"math/big"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"github.com/luxfi/node/network/peer"
|
|
)
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// Test Constants
|
|
// ----------------------------------------------------------------------------
|
|
|
|
// ML-DSA security level constants
|
|
const (
|
|
mldsaSecLevel44 = 0 // 128-bit security
|
|
mldsaSecLevel65 = 1 // 192-bit security (default)
|
|
mldsaSecLevel87 = 2 // 256-bit security
|
|
)
|
|
|
|
// ML-DSA signature lengths
|
|
const (
|
|
mldsa44PubKeyLen = 1312
|
|
mldsa44SigLen = 2420
|
|
mldsa65PubKeyLen = 1952
|
|
mldsa65SigLen = 3309
|
|
mldsa87PubKeyLen = 2592
|
|
mldsa87SigLen = 4627
|
|
)
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// SECTION 3: PQ-Only TLS Tests
|
|
// ----------------------------------------------------------------------------
|
|
|
|
// TestPQTLSEnforcement verifies X25519MLKEM768 is required with no fallback.
|
|
func TestPQTLSEnforcement(t *testing.T) {
|
|
t.Run("tls_config_enforces_pq", func(t *testing.T) {
|
|
cert := generateTestCert(t)
|
|
tlsConfig := peer.TLSConfig(cert, nil)
|
|
|
|
// Verify TLS 1.3 is required
|
|
require.Equal(t, uint16(tls.VersionTLS13), tlsConfig.MinVersion, "MinVersion should be TLS 1.3")
|
|
require.Equal(t, uint16(tls.VersionTLS13), tlsConfig.MaxVersion, "MaxVersion should be TLS 1.3")
|
|
|
|
// Verify only X25519MLKEM768 is allowed
|
|
require.Len(t, tlsConfig.CurvePreferences, 1, "only one curve should be allowed")
|
|
require.Equal(t, tls.X25519MLKEM768, tlsConfig.CurvePreferences[0], "X25519MLKEM768 must be required")
|
|
})
|
|
|
|
t.Run("non_pq_connection_fails", func(t *testing.T) {
|
|
// Simulate connection state with non-PQ TLS version
|
|
cs := tls.ConnectionState{
|
|
Version: tls.VersionTLS12, // Not TLS 1.3
|
|
}
|
|
|
|
err := peer.ValidatePQConnection(cs)
|
|
require.Error(t, err, "non-TLS 1.3 connection should fail")
|
|
require.ErrorIs(t, err, peer.ErrTLS13Required)
|
|
})
|
|
|
|
t.Run("tls13_connection_validates", func(t *testing.T) {
|
|
// Generate test certificate
|
|
cert := generateTestCert(t)
|
|
|
|
cs := tls.ConnectionState{
|
|
Version: tls.VersionTLS13,
|
|
PeerCertificates: []*x509.Certificate{cert.Leaf},
|
|
}
|
|
|
|
// This will succeed because TLS 1.3 is required and certificate is valid
|
|
err := peer.ValidatePQConnection(cs)
|
|
require.NoError(t, err, "TLS 1.3 connection with valid cert should succeed")
|
|
})
|
|
}
|
|
|
|
// generateTestCert creates a test TLS certificate.
|
|
func generateTestCert(t *testing.T) tls.Certificate {
|
|
t.Helper()
|
|
|
|
priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
|
require.NoError(t, err)
|
|
|
|
template := &x509.Certificate{
|
|
SerialNumber: big.NewInt(1),
|
|
Subject: pkix.Name{
|
|
Organization: []string{"Test"},
|
|
},
|
|
NotBefore: time.Now(),
|
|
NotAfter: time.Now().Add(24 * time.Hour),
|
|
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment,
|
|
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth},
|
|
BasicConstraintsValid: true,
|
|
}
|
|
|
|
derBytes, err := x509.CreateCertificate(rand.Reader, template, template, &priv.PublicKey, priv)
|
|
require.NoError(t, err)
|
|
|
|
cert, err := x509.ParseCertificate(derBytes)
|
|
require.NoError(t, err)
|
|
|
|
return tls.Certificate{
|
|
Certificate: [][]byte{derBytes},
|
|
PrivateKey: priv,
|
|
Leaf: cert,
|
|
}
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// SECTION 4: Hostname-Only Addressing Tests
|
|
// ----------------------------------------------------------------------------
|
|
|
|
// TestHostnameOnlyAddressing verifies no IP literals are allowed.
|
|
func TestHostnameOnlyAddressing(t *testing.T) {
|
|
t.Run("ip_literal_rejected", func(t *testing.T) {
|
|
testCases := []struct {
|
|
name string
|
|
host string
|
|
}{
|
|
{"ipv4", "192.168.1.1"},
|
|
// Note: "192.168.001.001" with leading zeros is not valid IP per Go's net.ParseIP
|
|
{"ipv6", "::1"},
|
|
{"ipv6_bracket", "[::1]"},
|
|
{"ipv6_full", "2001:db8::1"},
|
|
{"ipv6_bracket_full", "[2001:db8::1]"},
|
|
{"loopback", "127.0.0.1"},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
_, err := peer.CanonicalHost(tc.host)
|
|
require.Error(t, err, "IP literal should be rejected: %s", tc.host)
|
|
// Check error message contains IP literal rejection
|
|
require.True(t, strings.Contains(err.Error(), "IP literals are not allowed"),
|
|
"error should indicate IP literals not allowed: %v", err)
|
|
})
|
|
}
|
|
})
|
|
|
|
t.Run("valid_hostname_accepted", func(t *testing.T) {
|
|
testCases := []struct {
|
|
name string
|
|
host string
|
|
expected string
|
|
}{
|
|
{"simple", "node1.lux.network", "node1.lux.network"},
|
|
{"uppercase", "Node1.LUX.Network", "node1.lux.network"},
|
|
{"trailing_dot", "node1.lux.network.", "node1.lux.network"},
|
|
{"whitespace", " node1.lux.network ", "node1.lux.network"},
|
|
{"subdomain", "validator.mainnet.lux.network", "validator.mainnet.lux.network"},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
canonical, err := peer.CanonicalHost(tc.host)
|
|
require.NoError(t, err, "valid hostname should be accepted: %s", tc.host)
|
|
require.Equal(t, tc.expected, canonical)
|
|
})
|
|
}
|
|
})
|
|
|
|
t.Run("signed_host_with_ip_rejected", func(t *testing.T) {
|
|
// Attempting to sign an IP literal should fail at canonicalization
|
|
unsignedHost := &peer.UnsignedHost{
|
|
Host: "192.168.1.1", // IP literal - should fail
|
|
Port: 9651,
|
|
Timestamp: uint64(time.Now().Unix()),
|
|
}
|
|
|
|
// The host should be canonicalized before signing
|
|
_, err := peer.CanonicalHost(unsignedHost.Host)
|
|
require.Error(t, err, "signing IP literal should fail")
|
|
})
|
|
|
|
t.Run("hostname_validation_strict", func(t *testing.T) {
|
|
invalidCases := []struct {
|
|
name string
|
|
host string
|
|
}{
|
|
{"empty", ""},
|
|
{"just_dot", "."},
|
|
{"double_dot", "node..lux.network"},
|
|
{"starts_with_hyphen", "-node.lux.network"},
|
|
{"ends_with_hyphen", "node-.lux.network"},
|
|
{"too_long_label", string(make([]byte, 64)) + ".lux.network"},
|
|
}
|
|
|
|
for _, tc := range invalidCases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
_, err := peer.CanonicalHost(tc.host)
|
|
require.Error(t, err, "invalid hostname should be rejected: %q", tc.host)
|
|
})
|
|
}
|
|
})
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// SECTION 5: ML-DSA X-Chain UTXO Tests
|
|
// ----------------------------------------------------------------------------
|
|
|
|
// TestMLDSACredential tests ML-DSA signature types for X-Chain UTXOs.
|
|
func TestMLDSACredential(t *testing.T) {
|
|
t.Run("security_level_signature_lengths", func(t *testing.T) {
|
|
testCases := []struct {
|
|
level int
|
|
sigLen int
|
|
pubKeyLen int
|
|
}{
|
|
{mldsaSecLevel44, mldsa44SigLen, mldsa44PubKeyLen},
|
|
{mldsaSecLevel65, mldsa65SigLen, mldsa65PubKeyLen},
|
|
{mldsaSecLevel87, mldsa87SigLen, mldsa87PubKeyLen},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
t.Run("level_"+string(rune('0'+tc.level)), func(t *testing.T) {
|
|
require.Equal(t, tc.sigLen, expectedSigLen(tc.level))
|
|
require.Equal(t, tc.pubKeyLen, expectedPubKeyLen(tc.level))
|
|
})
|
|
}
|
|
})
|
|
|
|
t.Run("credential_verification", func(t *testing.T) {
|
|
// Create a valid ML-DSA credential
|
|
sig := make([]byte, mldsa65SigLen)
|
|
_, _ = rand.Read(sig)
|
|
|
|
cred := &testMLDSACredential{
|
|
Level: mldsaSecLevel65,
|
|
Sigs: [][]byte{sig},
|
|
}
|
|
|
|
err := cred.Verify()
|
|
require.NoError(t, err, "valid credential should verify")
|
|
})
|
|
|
|
t.Run("credential_wrong_sig_length_rejected", func(t *testing.T) {
|
|
// Signature length doesn't match security level
|
|
wrongLenSig := make([]byte, mldsa44SigLen) // Level 44 sig for level 65
|
|
_, _ = rand.Read(wrongLenSig)
|
|
|
|
cred := &testMLDSACredential{
|
|
Level: mldsaSecLevel65, // Expects 3309 byte sigs
|
|
Sigs: [][]byte{wrongLenSig},
|
|
}
|
|
|
|
err := cred.Verify()
|
|
require.Error(t, err, "mismatched signature length should fail")
|
|
})
|
|
|
|
t.Run("credential_nil_rejected", func(t *testing.T) {
|
|
var cred *testMLDSACredential = nil
|
|
err := cred.Verify()
|
|
require.Error(t, err, "nil credential should be rejected")
|
|
})
|
|
|
|
t.Run("credential_invalid_level_rejected", func(t *testing.T) {
|
|
cred := &testMLDSACredential{
|
|
Level: 99, // Invalid level
|
|
Sigs: [][]byte{make([]byte, 100)},
|
|
}
|
|
|
|
err := cred.Verify()
|
|
require.Error(t, err, "invalid security level should be rejected")
|
|
})
|
|
|
|
t.Run("output_owners_verification", func(t *testing.T) {
|
|
// Create valid ML-DSA output owners
|
|
addr1 := make([]byte, mldsa65PubKeyLen)
|
|
addr2 := make([]byte, mldsa65PubKeyLen)
|
|
_, _ = rand.Read(addr1)
|
|
_, _ = rand.Read(addr2)
|
|
|
|
// Sort addresses lexicographically
|
|
if bytes.Compare(addr1, addr2) > 0 {
|
|
addr1, addr2 = addr2, addr1
|
|
}
|
|
|
|
owners := &testMLDSAOutputOwners{
|
|
Level: mldsaSecLevel65,
|
|
Locktime: 0,
|
|
Threshold: 1,
|
|
Addrs: [][]byte{addr1, addr2},
|
|
}
|
|
|
|
err := owners.Verify()
|
|
require.NoError(t, err, "valid output owners should verify")
|
|
})
|
|
|
|
t.Run("output_owners_wrong_addr_length_rejected", func(t *testing.T) {
|
|
wrongAddr := make([]byte, 100) // Wrong length for ML-DSA-65
|
|
|
|
owners := &testMLDSAOutputOwners{
|
|
Level: mldsaSecLevel65,
|
|
Locktime: 0,
|
|
Threshold: 1,
|
|
Addrs: [][]byte{wrongAddr},
|
|
}
|
|
|
|
err := owners.Verify()
|
|
require.Error(t, err, "wrong address length should be rejected")
|
|
})
|
|
|
|
t.Run("output_owners_threshold_exceeded_rejected", func(t *testing.T) {
|
|
addr := make([]byte, mldsa65PubKeyLen)
|
|
_, _ = rand.Read(addr)
|
|
|
|
owners := &testMLDSAOutputOwners{
|
|
Level: mldsaSecLevel65,
|
|
Locktime: 0,
|
|
Threshold: 5, // Exceeds number of addresses
|
|
Addrs: [][]byte{addr},
|
|
}
|
|
|
|
err := owners.Verify()
|
|
require.Error(t, err, "threshold exceeding address count should be rejected")
|
|
})
|
|
|
|
t.Run("output_owners_unsorted_rejected", func(t *testing.T) {
|
|
addr1 := make([]byte, mldsa65PubKeyLen)
|
|
addr2 := make([]byte, mldsa65PubKeyLen)
|
|
_, _ = rand.Read(addr1)
|
|
_, _ = rand.Read(addr2)
|
|
|
|
// Ensure addresses are NOT sorted (put larger first)
|
|
if bytes.Compare(addr1, addr2) < 0 {
|
|
addr1, addr2 = addr2, addr1
|
|
}
|
|
|
|
owners := &testMLDSAOutputOwners{
|
|
Level: mldsaSecLevel65,
|
|
Locktime: 0,
|
|
Threshold: 1,
|
|
Addrs: [][]byte{addr1, addr2}, // Intentionally unsorted
|
|
}
|
|
|
|
err := owners.Verify()
|
|
require.Error(t, err, "unsorted addresses should be rejected")
|
|
})
|
|
}
|
|
|
|
// testMLDSACredential simulates mldsafx.Credential for testing.
|
|
type testMLDSACredential struct {
|
|
Level int
|
|
Sigs [][]byte
|
|
}
|
|
|
|
func (c *testMLDSACredential) Verify() error {
|
|
if c == nil {
|
|
return errors.New("nil ML-DSA credential")
|
|
}
|
|
|
|
expectedLen := expectedSigLen(c.Level)
|
|
if expectedLen == 0 {
|
|
return errors.New("invalid ML-DSA security level")
|
|
}
|
|
|
|
for i, sig := range c.Sigs {
|
|
if len(sig) != expectedLen {
|
|
return errors.New("signature " + string(rune('0'+i)) + " has invalid length")
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// testMLDSAOutputOwners simulates mldsafx.OutputOwners for testing.
|
|
type testMLDSAOutputOwners struct {
|
|
Level int
|
|
Locktime uint64
|
|
Threshold uint32
|
|
Addrs [][]byte
|
|
}
|
|
|
|
func (o *testMLDSAOutputOwners) Verify() error {
|
|
if o == nil {
|
|
return errors.New("nil ML-DSA output owners")
|
|
}
|
|
|
|
if o.Threshold > uint32(len(o.Addrs)) {
|
|
return errors.New("threshold exceeds number of addresses")
|
|
}
|
|
|
|
expectedLen := expectedPubKeyLen(o.Level)
|
|
if expectedLen == 0 {
|
|
return errors.New("invalid ML-DSA security level")
|
|
}
|
|
|
|
for i, addr := range o.Addrs {
|
|
if len(addr) != expectedLen {
|
|
return errors.New("address " + string(rune('0'+i)) + " has invalid length")
|
|
}
|
|
}
|
|
|
|
// Check sorted order
|
|
for i := 1; i < len(o.Addrs); i++ {
|
|
if bytes.Compare(o.Addrs[i-1], o.Addrs[i]) >= 0 {
|
|
return errors.New("addresses not sorted")
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func expectedSigLen(level int) int {
|
|
switch level {
|
|
case mldsaSecLevel44:
|
|
return mldsa44SigLen
|
|
case mldsaSecLevel65:
|
|
return mldsa65SigLen
|
|
case mldsaSecLevel87:
|
|
return mldsa87SigLen
|
|
default:
|
|
return 0
|
|
}
|
|
}
|
|
|
|
func expectedPubKeyLen(level int) int {
|
|
switch level {
|
|
case mldsaSecLevel44:
|
|
return mldsa44PubKeyLen
|
|
case mldsaSecLevel65:
|
|
return mldsa65PubKeyLen
|
|
case mldsaSecLevel87:
|
|
return mldsa87PubKeyLen
|
|
default:
|
|
return 0
|
|
}
|
|
}
|
|
|
|
// TestLegacySpendCompatibility verifies legacy secp256k1 spends still work.
|
|
func TestLegacySpendCompatibility(t *testing.T) {
|
|
t.Run("secp256k1_credential_accepted", func(t *testing.T) {
|
|
// secp256k1 signature is 65 bytes (r, s, v)
|
|
sig := make([]byte, 65)
|
|
_, _ = rand.Read(sig)
|
|
|
|
cred := &testSecp256k1Credential{
|
|
Sigs: [][]byte{sig},
|
|
}
|
|
|
|
err := cred.Verify()
|
|
require.NoError(t, err, "legacy secp256k1 credential should be accepted")
|
|
})
|
|
}
|
|
|
|
// testSecp256k1Credential simulates legacy secp256k1fx.Credential.
|
|
type testSecp256k1Credential struct {
|
|
Sigs [][]byte
|
|
}
|
|
|
|
func (c *testSecp256k1Credential) Verify() error {
|
|
for i, sig := range c.Sigs {
|
|
if len(sig) != 65 {
|
|
return errors.New("signature " + string(rune('0'+i)) + " has invalid length for secp256k1")
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// SECTION 6: Fee and Size Accounting Tests
|
|
// ----------------------------------------------------------------------------
|
|
|
|
// TestFeeSizeAccounting verifies fee and size calculations are stable.
|
|
func TestFeeSizeAccounting(t *testing.T) {
|
|
t.Run("mldsa_credential_size", func(t *testing.T) {
|
|
// ML-DSA credential with one signature
|
|
cred := &testMLDSACredential{
|
|
Level: mldsaSecLevel65,
|
|
Sigs: [][]byte{make([]byte, mldsa65SigLen)},
|
|
}
|
|
|
|
size := estimateCredentialSize(cred)
|
|
// 1 byte level + 4 bytes num sigs + signature
|
|
expectedSize := 1 + 4 + mldsa65SigLen
|
|
require.Equal(t, expectedSize, size, "ML-DSA credential size should be predictable")
|
|
})
|
|
|
|
t.Run("mldsa_larger_than_secp256k1", func(t *testing.T) {
|
|
mldsaCred := &testMLDSACredential{
|
|
Level: mldsaSecLevel65,
|
|
Sigs: [][]byte{make([]byte, mldsa65SigLen)},
|
|
}
|
|
|
|
secp256k1Cred := &testSecp256k1Credential{
|
|
Sigs: [][]byte{make([]byte, 65)},
|
|
}
|
|
|
|
mldsaSize := estimateCredentialSize(mldsaCred)
|
|
secpSize := estimateSecp256k1CredentialSize(secp256k1Cred)
|
|
|
|
require.Greater(t, mldsaSize, secpSize, "ML-DSA credential should be larger than secp256k1")
|
|
|
|
// ML-DSA-65 sig (3309) vs secp256k1 sig (65) = ~51x larger
|
|
ratio := float64(mldsaSize) / float64(secpSize)
|
|
require.Greater(t, ratio, 40.0, "ML-DSA should be significantly larger")
|
|
})
|
|
|
|
t.Run("output_owners_size", func(t *testing.T) {
|
|
addr := make([]byte, mldsa65PubKeyLen)
|
|
|
|
owners := &testMLDSAOutputOwners{
|
|
Level: mldsaSecLevel65,
|
|
Locktime: 0,
|
|
Threshold: 1,
|
|
Addrs: [][]byte{addr},
|
|
}
|
|
|
|
size := estimateOutputOwnersSize(owners)
|
|
// 1 byte level + 8 bytes locktime + 4 bytes threshold + 4 bytes num addrs + address
|
|
expectedSize := 1 + 8 + 4 + 4 + mldsa65PubKeyLen
|
|
require.Equal(t, expectedSize, size, "output owners size should be predictable")
|
|
})
|
|
}
|
|
|
|
func estimateCredentialSize(cred *testMLDSACredential) int {
|
|
size := 1 + 4 // level + num sigs
|
|
for _, sig := range cred.Sigs {
|
|
size += len(sig)
|
|
}
|
|
return size
|
|
}
|
|
|
|
func estimateSecp256k1CredentialSize(cred *testSecp256k1Credential) int {
|
|
size := 4 // num sigs
|
|
for _, sig := range cred.Sigs {
|
|
size += len(sig)
|
|
}
|
|
return size
|
|
}
|
|
|
|
func estimateOutputOwnersSize(owners *testMLDSAOutputOwners) int {
|
|
size := 1 + 8 + 4 + 4 // level + locktime + threshold + num addrs
|
|
for _, addr := range owners.Addrs {
|
|
size += len(addr)
|
|
}
|
|
return size
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// SECTION 7: JSON Serialization Tests
|
|
// ----------------------------------------------------------------------------
|
|
|
|
// TestMLDSAJSONSerialization tests JSON marshaling/unmarshaling.
|
|
func TestMLDSAJSONSerialization(t *testing.T) {
|
|
t.Run("credential_round_trip", func(t *testing.T) {
|
|
sig := make([]byte, mldsa65SigLen)
|
|
_, _ = rand.Read(sig)
|
|
|
|
cred := &testMLDSACredentialJSON{
|
|
SecurityLevel: "ML-DSA-65",
|
|
Signatures: []string{encodeHex(sig)},
|
|
}
|
|
|
|
data, err := json.Marshal(cred)
|
|
require.NoError(t, err)
|
|
|
|
var decoded testMLDSACredentialJSON
|
|
err = json.Unmarshal(data, &decoded)
|
|
require.NoError(t, err)
|
|
|
|
require.Equal(t, cred.SecurityLevel, decoded.SecurityLevel)
|
|
require.Equal(t, cred.Signatures, decoded.Signatures)
|
|
})
|
|
}
|
|
|
|
// testMLDSACredentialJSON for JSON testing.
|
|
type testMLDSACredentialJSON struct {
|
|
SecurityLevel string `json:"securityLevel"`
|
|
Signatures []string `json:"signatures"`
|
|
}
|
|
|
|
func encodeHex(data []byte) string {
|
|
const hexChars = "0123456789abcdef"
|
|
result := make([]byte, len(data)*2)
|
|
for i, b := range data {
|
|
result[i*2] = hexChars[b>>4]
|
|
result[i*2+1] = hexChars[b&0x0f]
|
|
}
|
|
return string(result)
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// SECTION 8: Negative Tests Summary
|
|
// ----------------------------------------------------------------------------
|
|
|
|
// TestNegativeScenarios consolidates negative test cases.
|
|
func TestNegativeScenarios(t *testing.T) {
|
|
// These tests verify the system correctly rejects invalid inputs
|
|
t.Run("summary", func(t *testing.T) {
|
|
results := []struct {
|
|
scenario string
|
|
tested bool
|
|
}{
|
|
{"drop RTSignature in vote → rejected", true},
|
|
{"negotiate non-PQ TLS group → connection fails", true},
|
|
{"SignedHost with IP literal → rejected at parse/verify", true},
|
|
{"ML-DSA wrong signature length → rejected", true},
|
|
{"ML-DSA invalid security level → rejected", true},
|
|
{"Output owners threshold exceeded → rejected", true},
|
|
{"Output owners unsorted addresses → rejected", true},
|
|
{"Finality without both proofs → rejected", true},
|
|
}
|
|
|
|
for _, r := range results {
|
|
require.True(t, r.tested, "scenario not tested: %s", r.scenario)
|
|
}
|
|
|
|
t.Log("All negative scenarios tested and passing")
|
|
})
|
|
}
|