ceremonyclient/node/execution/intrinsics/token/token_serialization_fuzz_test.go
Cassandra Heart dbd95bd9e9
v2.1.0 (#439)
* v2.1.0 [omit consensus and adjacent] - this commit will be amended with the full release after the file copy is complete

* 2.1.0 main node rollup
2025-09-30 02:48:15 -05:00

607 lines
17 KiB
Go

package token_test
import (
"bytes"
"encoding/binary"
"math/big"
"testing"
"github.com/stretchr/testify/require"
"source.quilibrium.com/quilibrium/monorepo/node/execution/intrinsics/token"
"source.quilibrium.com/quilibrium/monorepo/protobufs"
"source.quilibrium.com/quilibrium/monorepo/types/mocks"
)
// FuzzRecipientBundleSerialization tests RecipientBundle serialization/deserialization
func FuzzRecipientBundleSerialization(f *testing.F) {
// Add seed corpus
f.Add([]byte{0x01, 0x02, 0x03}, []byte{0x04, 0x05, 0x06}, []byte{0x07, 0x08, 0x09}, []byte{0x0a, 0x0b, 0x0c}, []byte{}, []byte{})
f.Add(make([]byte, 32), make([]byte, 57), make([]byte, 32), make([]byte, 32), make([]byte, 32), make([]byte, 57))
f.Add(make([]byte, 57), make([]byte, 57), make([]byte, 16), make([]byte, 16), []byte{}, []byte{})
f.Fuzz(func(t *testing.T, oneTimeKey, verificationKey, coinBalance, mask, addRef, addRefKey []byte) {
// Create recipient bundle with fuzz data
rb := &token.RecipientBundle{
OneTimeKey: oneTimeKey,
VerificationKey: verificationKey,
CoinBalance: coinBalance,
Mask: mask,
AdditionalReference: addRef,
AdditionalReferenceKey: addRefKey,
}
// Serialize
data, err := rb.ToBytes()
if err != nil {
// Serialization can fail for invalid inputs, which is expected
return
}
// Deserialize
rb2 := &token.RecipientBundle{}
err = rb2.FromBytes(data)
// If deserialization succeeds, verify round-trip
if err == nil {
require.Equal(t, rb.OneTimeKey, rb2.OneTimeKey)
require.Equal(t, rb.VerificationKey, rb2.VerificationKey)
require.Equal(t, rb.CoinBalance, rb2.CoinBalance)
require.Equal(t, rb.Mask, rb2.Mask)
require.Equal(t, rb.AdditionalReference, rb2.AdditionalReference)
require.Equal(t, rb.AdditionalReferenceKey, rb2.AdditionalReferenceKey)
}
// If deserialization fails, that's acceptable for malformed input
})
}
// FuzzTransactionOutputSerialization tests TransactionOutput serialization/deserialization
func FuzzTransactionOutputSerialization(f *testing.F) {
// Add seed corpus
f.Add([]byte{0x01}, []byte{0x02}, []byte{0x03}, []byte{0x04}, []byte{0x05}, []byte{0x06})
f.Add(make([]byte, 8), make([]byte, 74), make([]byte, 32), make([]byte, 57), make([]byte, 32), make([]byte, 32))
f.Fuzz(func(t *testing.T, frameNumber, commitment, oneTimeKey, verificationKey, coinBalance, mask []byte) {
// Create transaction output with fuzz data
output := &token.TransactionOutput{
FrameNumber: frameNumber,
Commitment: commitment,
RecipientOutput: token.RecipientBundle{
OneTimeKey: oneTimeKey,
VerificationKey: verificationKey,
CoinBalance: coinBalance,
Mask: mask,
},
}
// Serialize
data, err := output.ToBytes()
if err != nil {
// Serialization can fail for invalid inputs
return
}
// Deserialize
output2 := &token.TransactionOutput{}
err = output2.FromBytes(data)
// If deserialization succeeds, verify round-trip
if err == nil {
require.Equal(t, output.FrameNumber, output2.FrameNumber)
require.Equal(t, output.Commitment, output2.Commitment)
require.Equal(t, output.RecipientOutput.OneTimeKey, output2.RecipientOutput.OneTimeKey)
require.Equal(t, output.RecipientOutput.VerificationKey, output2.RecipientOutput.VerificationKey)
require.Equal(t, output.RecipientOutput.CoinBalance, output2.RecipientOutput.CoinBalance)
require.Equal(t, output.RecipientOutput.Mask, output2.RecipientOutput.Mask)
}
})
}
// FuzzTransactionInputSerialization tests TransactionInput serialization/deserialization
func FuzzTransactionInputSerialization(f *testing.F) {
// Add seed corpus with various lengths
f.Add([]byte{0x01}, []byte{0x02}, 0)
f.Add(make([]byte, 74), make([]byte, 114), 3)
f.Fuzz(func(t *testing.T, commitment, signature []byte, numProofs int) {
// Limit proofs to reasonable number
if numProofs < 0 || numProofs > 100 {
return
}
mockHG := new(mocks.MockHypergraph)
mockIP := new(mocks.MockInclusionProver)
setupMockHypergraph(mockHG, mockIP)
// Create transaction input with fuzz data
input := &token.TransactionInput{
Commitment: commitment,
Signature: signature,
Proofs: make([][]byte, numProofs),
}
// Generate random proofs
for i := 0; i < numProofs; i++ {
input.Proofs[i] = make([]byte, 32)
}
// Serialize
data, err := input.ToBytes()
if err != nil {
return
}
// Deserialize
input2 := &token.TransactionInput{}
err = input2.FromBytes(data)
// If deserialization succeeds, verify basic fields
if err == nil {
require.Equal(t, input.Commitment, input2.Commitment)
require.Equal(t, input.Signature, input2.Signature)
require.Equal(t, len(input.Proofs), len(input2.Proofs))
}
})
}
// FuzzTokenConfigurationSerialization tests TokenConfiguration serialization/deserialization
func FuzzTokenConfigurationSerialization(f *testing.F) {
// Add seed corpus
f.Add("Token", "TKN", uint16(1), int64(100), int64(1000000))
f.Add("", "", uint16(0), int64(1), int64(0))
f.Add("A"+string(make([]byte, 100)), "B", uint16(255), int64(8), int64(^int64(0)>>1))
f.Fuzz(func(t *testing.T, name, symbol string, behavior uint16, units int64, supply int64) {
// Skip extremely large inputs
if len(name) > 10000 || len(symbol) > 10000 {
t.Skip("Input too large")
}
if units < 0 || supply < 0 {
return // Skip negative values
}
config := &token.TokenIntrinsicConfiguration{
Name: name,
Symbol: symbol,
Behavior: token.TokenIntrinsicBehavior(behavior),
Units: big.NewInt(units),
Supply: big.NewInt(supply),
}
// Convert to protobuf
pb := config.ToProtobuf()
// Serialize using protobuf
data, err := pb.ToCanonicalBytes()
if err != nil {
return
}
// Deserialize using protobuf
pb2 := &protobufs.TokenConfiguration{}
err = pb2.FromCanonicalBytes(data)
if err != nil {
return
}
// Convert back from protobuf
config2, err := token.TokenConfigurationFromProtobuf(pb2)
// If deserialization succeeds, verify round-trip
if err == nil && config2 != nil {
require.Equal(t, config.Name, config2.Name)
require.Equal(t, config.Symbol, config2.Symbol)
require.Equal(t, config.Behavior, config2.Behavior)
if config.Units != nil && config2.Units != nil {
require.Equal(t, config.Units.Cmp(config2.Units), 0)
}
if config.Supply != nil && config2.Supply != nil {
require.Equal(t, config.Supply.Cmp(config2.Supply), 0)
}
}
})
}
// FuzzDeserializationRobustness tests deserialization with completely random data
func FuzzDeserializationRobustness(f *testing.F) {
// Add various malformed inputs
f.Add([]byte{})
f.Add([]byte{0x00})
f.Add([]byte{0x00, 0x00, 0x00, 0x01}) // Just type prefix
f.Add([]byte{0xff, 0xff, 0xff, 0xff}) // Invalid type
f.Add(bytes.Repeat([]byte{0x00}, 1000))
f.Add(bytes.Repeat([]byte{0xff}, 1000))
// Add some structured but potentially malformed data
f.Add(append([]byte{0x00, 0x00, 0x00, 0x01}, bytes.Repeat([]byte{0x41}, 100)...))
f.Add(append([]byte{0x00, 0x00, 0x00, 0x02}, bytes.Repeat([]byte{0x42}, 200)...))
f.Fuzz(func(t *testing.T, data []byte) {
// Test all deserialization functions with random data
// They should either succeed or fail gracefully without panicking
// Test TokenConfiguration
pbConfig := &protobufs.TokenConfiguration{}
_ = pbConfig.FromCanonicalBytes(data)
if pbConfig != nil {
config, _ := token.TokenConfigurationFromProtobuf(pbConfig)
_ = config
}
// Test RecipientBundle
rb := &token.RecipientBundle{}
_ = rb.FromBytes(data)
// Test TransactionOutput
output := &token.TransactionOutput{}
_ = output.FromBytes(data)
// Test TransactionInput
mockHG := new(mocks.MockHypergraph)
mockIP := new(mocks.MockInclusionProver)
setupMockHypergraph(mockHG, mockIP)
input := &token.TransactionInput{}
_ = input.FromBytes(data)
// Test PendingTransactionInput
ptInput := &token.PendingTransactionInput{}
_ = ptInput.FromBytes(data)
// Test PendingTransactionOutput
ptOutput := &token.PendingTransactionOutput{}
_ = ptOutput.FromBytes(data)
// Test MintTransactionInput
mtInput := &token.MintTransactionInput{}
_ = mtInput.FromBytes(data)
// Test MintTransactionOutput
mtOutput := &token.MintTransactionOutput{}
_ = mtOutput.FromBytes(data)
})
}
// Specific deserialization-focused fuzz tests for robustness against malformed inputs
func FuzzRecipientBundle_Deserialization(f *testing.F) {
// Add valid case
validRB := &token.RecipientBundle{
OneTimeKey: make([]byte, 56),
VerificationKey: make([]byte, 56),
CoinBalance: make([]byte, 56),
Mask: make([]byte, 56),
AdditionalReference: make([]byte, 64), // Can be 0 or 64
AdditionalReferenceKey: make([]byte, 56), // Can be 0 or 56
}
validData, _ := validRB.ToBytes()
f.Add(validData)
// Add truncated data
for i := 0; i < len(validData) && i < 100; i++ {
f.Add(validData[:i])
}
// Add invalid length combinations
f.Add([]byte{0x00, 0x00, 0x00, 0x10}) // Invalid OneTimeKey length (16 instead of 56)
f.Add([]byte{0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x10}) // Valid OneTimeKey, invalid VerificationKey
f.Fuzz(func(t *testing.T, data []byte) {
if len(data) > 1000000 {
t.Skip("Skipping very large input")
}
rb := &token.RecipientBundle{}
_ = rb.FromBytes(data) // Should not panic
})
}
func FuzzTransactionOutput_Deserialization(f *testing.F) {
// Add valid case
validOutput := &token.TransactionOutput{
FrameNumber: make([]byte, 8),
Commitment: make([]byte, 56),
RecipientOutput: token.RecipientBundle{
OneTimeKey: make([]byte, 56),
VerificationKey: make([]byte, 56),
CoinBalance: make([]byte, 56),
Mask: make([]byte, 56),
},
}
validData, _ := validOutput.ToBytes()
f.Add(validData)
// Add truncated data
for i := 0; i < len(validData) && i < 100; i++ {
f.Add(validData[:i])
}
f.Fuzz(func(t *testing.T, data []byte) {
if len(data) > 1000000 {
t.Skip("Skipping very large input")
}
output := &token.TransactionOutput{}
_ = output.FromBytes(data) // Should not panic
})
}
func FuzzTransactionInput_Deserialization(f *testing.F) {
// Add valid case
validInput := &token.TransactionInput{
Commitment: make([]byte, 56),
Signature: make([]byte, 114),
Proofs: [][]byte{make([]byte, 32), make([]byte, 32)},
}
validData, _ := validInput.ToBytes()
f.Add(validData)
// Add truncated data
for i := 0; i < len(validData) && i < 50; i++ {
f.Add(validData[:i])
}
f.Fuzz(func(t *testing.T, data []byte) {
if len(data) > 1000000 {
t.Skip("Skipping very large input")
}
mockHG := new(mocks.MockHypergraph)
mockIP := new(mocks.MockInclusionProver)
setupMockHypergraph(mockHG, mockIP)
input := &token.TransactionInput{}
_ = input.FromBytes(data) // Should not panic
})
}
func FuzzPendingTransactionInput_Deserialization(f *testing.F) {
// Add valid case
validInput := &token.PendingTransactionInput{
Commitment: make([]byte, 56),
Signature: make([]byte, 114),
Proofs: [][]byte{make([]byte, 32)},
}
validData, _ := validInput.ToBytes()
f.Add(validData)
// Add truncated data
for i := 0; i < len(validData) && i < 50; i++ {
f.Add(validData[:i])
}
f.Fuzz(func(t *testing.T, data []byte) {
if len(data) > 1000000 {
t.Skip("Skipping very large input")
}
mockHG := new(mocks.MockHypergraph)
mockIP := new(mocks.MockInclusionProver)
setupMockHypergraph(mockHG, mockIP)
input := &token.PendingTransactionInput{}
_ = input.FromBytes(data) // Should not panic
})
}
func FuzzPendingTransactionOutput_Deserialization(f *testing.F) {
// Add valid case
validOutput := &token.PendingTransactionOutput{
FrameNumber: make([]byte, 8),
Commitment: make([]byte, 56),
ToOutput: token.RecipientBundle{
OneTimeKey: make([]byte, 56),
VerificationKey: make([]byte, 56),
CoinBalance: make([]byte, 56),
Mask: make([]byte, 56),
},
RefundOutput: token.RecipientBundle{
OneTimeKey: make([]byte, 56),
VerificationKey: make([]byte, 56),
CoinBalance: make([]byte, 56),
Mask: make([]byte, 56),
},
Expiration: 12345,
}
validData, _ := validOutput.ToBytes()
f.Add(validData)
// Add truncated data
for i := 0; i < len(validData) && i < 100; i++ {
f.Add(validData[:i])
}
f.Fuzz(func(t *testing.T, data []byte) {
if len(data) > 1000000 {
t.Skip("Skipping very large input")
}
output := &token.PendingTransactionOutput{}
_ = output.FromBytes(data) // Should not panic
})
}
func FuzzMintTransactionInput_Deserialization(f *testing.F) {
// Add valid case
validInput := &token.MintTransactionInput{
Value: big.NewInt(1000),
Commitment: make([]byte, 56),
Signature: make([]byte, 114),
Proofs: [][]byte{make([]byte, 32)},
AdditionalReference: make([]byte, 64),
AdditionalReferenceKey: make([]byte, 56),
}
validData, _ := validInput.ToBytes()
f.Add(validData)
// Add case without additional reference
validInputNoRef := &token.MintTransactionInput{
Value: big.NewInt(1000),
Commitment: make([]byte, 56),
Signature: make([]byte, 114),
Proofs: [][]byte{make([]byte, 32)},
}
validDataNoRef, _ := validInputNoRef.ToBytes()
f.Add(validDataNoRef)
// Add truncated data
for i := 0; i < len(validData) && i < 50; i++ {
f.Add(validData[:i])
}
f.Fuzz(func(t *testing.T, data []byte) {
if len(data) > 1000000 {
t.Skip("Skipping very large input")
}
mockHG := new(mocks.MockHypergraph)
mockIP := new(mocks.MockInclusionProver)
setupMockHypergraph(mockHG, mockIP)
input := &token.MintTransactionInput{}
_ = input.FromBytes(data) // Should not panic
})
}
func FuzzMintTransactionOutput_Deserialization(f *testing.F) {
// Add valid case
validOutput := &token.MintTransactionOutput{
FrameNumber: make([]byte, 8),
Commitment: make([]byte, 56),
RecipientOutput: token.RecipientBundle{
OneTimeKey: make([]byte, 56),
VerificationKey: make([]byte, 56),
CoinBalance: make([]byte, 56),
Mask: make([]byte, 56),
},
}
validData, _ := validOutput.ToBytes()
f.Add(validData)
// Add truncated data
for i := 0; i < len(validData) && i < 100; i++ {
f.Add(validData[:i])
}
f.Fuzz(func(t *testing.T, data []byte) {
if len(data) > 1000000 {
t.Skip("Skipping very large input")
}
output := &token.MintTransactionOutput{}
_ = output.FromBytes(data) // Should not panic
})
}
func FuzzTokenConfiguration_Deserialization(f *testing.F) {
// Add valid case
validConfig := &token.TokenIntrinsicConfiguration{
Name: "Test Token",
Symbol: "TT",
Behavior: token.TokenIntrinsicBehavior(1),
Units: big.NewInt(18),
Supply: big.NewInt(1000000),
}
pb := validConfig.ToProtobuf()
validData, _ := pb.ToCanonicalBytes()
f.Add(validData)
// Add truncated data
for i := 0; i < len(validData) && i < 50; i++ {
f.Add(validData[:i])
}
// Add invalid type prefix
f.Add([]byte{0x00, 0x00, 0x00, 0x99}) // Wrong type prefix
f.Fuzz(func(t *testing.T, data []byte) {
if len(data) > 1000000 {
t.Skip("Skipping very large input")
}
pbConfig := &protobufs.TokenConfiguration{}
_ = pbConfig.FromCanonicalBytes(data)
if pbConfig != nil {
_, _ = token.TokenConfigurationFromProtobuf(pbConfig) // Should not panic
}
})
}
func FuzzInvalidLengthFields(f *testing.F) {
// Add cases with invalid length fields
f.Add(uint32(0xFFFFFFFF), uint32(0xFFFFFFFF))
f.Add(uint32(1<<31), uint32(1<<31))
f.Add(uint32(1000000), uint32(1000000))
f.Add(uint32(0), uint32(100)) // Zero length with data
f.Add(uint32(100), uint32(0)) // Data length with zero
f.Fuzz(func(t *testing.T, len1, len2 uint32) {
// Create data with potentially overflowing lengths
buf := new(bytes.Buffer)
// Write a valid-looking structure with bad lengths
binary.Write(buf, binary.BigEndian, len1) // OneTimeKey length
buf.Write(make([]byte, min(int(len1), 100)))
binary.Write(buf, binary.BigEndian, len2) // VerificationKey length
buf.Write(make([]byte, min(int(len2), 100)))
// Try to deserialize - should handle gracefully
rb := &token.RecipientBundle{}
_ = rb.FromBytes(buf.Bytes())
})
}
func FuzzProofArrayHandling(f *testing.F) {
// Add cases with various proof array sizes
f.Add(uint32(0), uint32(0))
f.Add(uint32(1), uint32(32))
f.Add(uint32(10), uint32(32))
f.Add(uint32(1000), uint32(32)) // Many proofs
f.Add(uint32(5), uint32(1000000)) // Large proof size
f.Fuzz(func(t *testing.T, numProofs, proofSize uint32) {
// Limit to reasonable values
if numProofs > 1000 || proofSize > 100000 {
return
}
// Create transaction input with fuzz number of proofs
buf := new(bytes.Buffer)
// Write commitment
binary.Write(buf, binary.BigEndian, uint32(56))
buf.Write(make([]byte, 56))
// Write signature
binary.Write(buf, binary.BigEndian, uint32(114))
buf.Write(make([]byte, 114))
// Write proofs
binary.Write(buf, binary.BigEndian, numProofs)
for i := uint32(0); i < min32(numProofs, 100); i++ {
binary.Write(buf, binary.BigEndian, min32(proofSize, 1000))
buf.Write(make([]byte, min32(proofSize, 1000)))
}
// Try to deserialize
mockHG := new(mocks.MockHypergraph)
mockIP := new(mocks.MockInclusionProver)
setupMockHypergraph(mockHG, mockIP)
input := &token.TransactionInput{}
_ = input.FromBytes(buf.Bytes())
})
}
func min32(a, b uint32) uint32 {
if a < b {
return a
}
return b
}
func min(a, b int) int {
if a < b {
return a
}
return b
}