Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 25 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
.PHONY: build_examples clean test check fix help integration docs
.PHONY: build_examples clean test check fix help integration docs fuzz fuzz-all

# Default target
all: check
Expand Down Expand Up @@ -45,3 +45,27 @@ docs:
echo "Documentation available at http://localhost:6060/pkg/github.com/tempoxyz/tempo-go/"
echo "Press Ctrl+C to stop the server"
@godoc -http=:6060

# Fuzz test duration (default 10s, override with FUZZTIME=1m)
FUZZTIME ?= 10s

# Run a single fuzz test: make fuzz FUZZ=FuzzTestName PKG=./pkg/transaction/
fuzz:
ifndef FUZZ
$(error Usage: make fuzz FUZZ=FuzzTestName PKG=./pkg/package/)
endif
ifndef PKG
$(error Usage: make fuzz FUZZ=FuzzTestName PKG=./pkg/package/)
endif
go test -fuzz=$(FUZZ) -fuzztime=$(FUZZTIME) $(PKG)

# Run all fuzz tests sequentially
fuzz-all:
@for pkg in ./pkg/transaction ./pkg/signer; do \
echo "=== Fuzzing $$pkg ==="; \
for test in $$(go test -list 'Fuzz.*' $$pkg 2>/dev/null | grep '^Fuzz'); do \
echo "Running $$test..."; \
go test -fuzz=$$test -fuzztime=$(FUZZTIME) $$pkg || exit 1; \
done; \
done
@echo "=== All fuzz tests complete ==="
71 changes: 71 additions & 0 deletions pkg/signer/fuzz_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package signer

import (
"math/big"
"testing"

"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func FuzzSignAndVerify(f *testing.F) {
f.Add([]byte("test message"))
f.Add([]byte{})
f.Add([]byte{0x00})
f.Add([]byte{0xff, 0xff, 0xff, 0xff})
f.Add(make([]byte, 1024))

f.Fuzz(func(t *testing.T, data []byte) {
privateKey, err := crypto.GenerateKey()
if err != nil {
return
}
sgn := NewSignerFromKey(privateKey)

sig, err := sgn.SignData(data)
require.NoError(t, err)

require.NotNil(t, sig.R)
require.NotNil(t, sig.S)
assert.LessOrEqual(t, sig.YParity, uint8(1))

v := sig.V()
assert.True(t, v == 27 || v == 28)

hash := crypto.Keccak256Hash(data)
valid, err := sgn.VerifySignature(hash, sig)
require.NoError(t, err)
assert.True(t, valid)

recoveredAddr, err := RecoverAddress(hash, sig)
require.NoError(t, err)
assert.Equal(t, sgn.Address(), recoveredAddr)
})
}

func FuzzRecoverAddressWithMalformedSignature(f *testing.F) {
f.Add(
[]byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01},
[]byte{0x01},
[]byte{0x01},
uint8(0),
)

f.Fuzz(func(t *testing.T, hashBytes, rBytes, sBytes []byte, yParity uint8) {
var hash common.Hash
if len(hashBytes) >= 32 {
copy(hash[:], hashBytes[:32])
} else if len(hashBytes) > 0 {
copy(hash[32-len(hashBytes):], hashBytes)
}

r := new(big.Int).SetBytes(rBytes)
s := new(big.Int).SetBytes(sBytes)
sig := NewSignature(r, s, yParity)

_, _ = RecoverAddress(hash, sig)
})
}
159 changes: 159 additions & 0 deletions pkg/transaction/fuzz_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
package transaction

import (
"bytes"
"math/big"
"testing"

"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/tempoxyz/tempo-go/pkg/signer"
)

func FuzzSerializeDeserializeRoundTrip(f *testing.F) {
f.Add(
uint64(42424),
uint64(1000000000),
uint64(2000000000),
uint64(21000),
uint64(0),
uint64(1),
uint64(0),
uint64(0),
[]byte{0x12, 0x34, 0x56, 0x78, 0x90, 0x12, 0x34, 0x56, 0x78, 0x90, 0x12, 0x34, 0x56, 0x78, 0x90, 0x12, 0x34, 0x56, 0x78, 0x90},
[]byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01},
[]byte{0xaa, 0xbb, 0xcc},
)

f.Fuzz(func(t *testing.T,
chainID uint64,
maxPriorityFeePerGas uint64,
maxFeePerGas uint64,
gas uint64,
nonceKey uint64,
nonce uint64,
validBefore uint64,
validAfter uint64,
toAddressBytes []byte,
valueBytes []byte,
callData []byte,
) {
if chainID == 0 || gas == 0 {
return
}

var toAddress common.Address
if len(toAddressBytes) >= 20 {
copy(toAddress[:], toAddressBytes[:20])
} else if len(toAddressBytes) > 0 {
copy(toAddress[20-len(toAddressBytes):], toAddressBytes)
}

value := new(big.Int)
if len(valueBytes) > 0 {
value.SetBytes(valueBytes)
}

tx := NewBuilder(big.NewInt(int64(chainID))).
SetMaxPriorityFeePerGas(big.NewInt(int64(maxPriorityFeePerGas))).
SetMaxFeePerGas(big.NewInt(int64(maxFeePerGas))).
SetGas(gas).
SetNonceKey(big.NewInt(int64(nonceKey))).
SetNonce(nonce).
SetValidBefore(validBefore).
SetValidAfter(validAfter).
AddCall(toAddress, value, callData).
Build()

serialized, err := Serialize(tx, nil)
if err != nil {
return
}

deserializedTx, err := Deserialize(serialized)
require.NoError(t, err)

assert.Equal(t, 0, tx.ChainID.Cmp(deserializedTx.ChainID))
assert.Equal(t, tx.Gas, deserializedTx.Gas)
assert.Equal(t, tx.Nonce, deserializedTx.Nonce)
assert.Equal(t, 0, tx.NonceKey.Cmp(deserializedTx.NonceKey))
assert.Equal(t, tx.ValidBefore, deserializedTx.ValidBefore)
assert.Equal(t, tx.ValidAfter, deserializedTx.ValidAfter)
assert.Len(t, deserializedTx.Calls, len(tx.Calls))

if len(tx.Calls) > 0 && len(deserializedTx.Calls) > 0 {
assert.Equal(t, 0, tx.Calls[0].Value.Cmp(deserializedTx.Calls[0].Value))
assert.True(t, bytes.Equal(tx.Calls[0].Data, deserializedTx.Calls[0].Data))
}
})
}

func FuzzDeserializeMalformed(f *testing.F) {
f.Add([]byte("0x76"))
f.Add([]byte("0x78"))
f.Add([]byte("0x00"))
f.Add([]byte{})
f.Add([]byte("76f83b82a5e880808094123456789012345678901234567890123456789080c0808080808080c0"))
f.Add([]byte("0x76f83b82a5e880808094123456789012345678901234567890123456789080c0808080808080c0"))

f.Fuzz(func(t *testing.T, data []byte) {
_, _ = Deserialize(string(data))
})
}

func FuzzSignedTransactionRoundTrip(f *testing.F) {
f.Add(
uint64(42424),
uint64(21000),
[]byte{0x12, 0x34, 0x56, 0x78, 0x90, 0x12, 0x34, 0x56, 0x78, 0x90, 0x12, 0x34, 0x56, 0x78, 0x90, 0x12, 0x34, 0x56, 0x78, 0x90},
[]byte{0xaa, 0xbb},
)

f.Fuzz(func(t *testing.T,
chainID uint64,
gas uint64,
toAddressBytes []byte,
callData []byte,
) {
if chainID == 0 || gas == 0 {
return
}

// grab 20 bytes to common.Address
var toAddress common.Address
if len(toAddressBytes) >= 20 {
copy(toAddress[:], toAddressBytes[:20])
} else if len(toAddressBytes) > 0 {
copy(toAddress[20-len(toAddressBytes):], toAddressBytes)
}

privateKey, err := crypto.GenerateKey()
if err != nil {
return
}
sgn := signer.NewSignerFromKey(privateKey)

tx := NewBuilder(big.NewInt(int64(chainID))).
SetGas(gas).
AddCall(toAddress, big.NewInt(0), callData).
Build()

err = SignTransaction(tx, sgn)
if err != nil {
return
}

serialized, err := Serialize(tx, nil)
require.NoError(t, err)

deserializedTx, err := Deserialize(serialized)
require.NoError(t, err)

recoveredAddr, err := VerifySignature(deserializedTx)
require.NoError(t, err)

assert.Equal(t, sgn.Address(), recoveredAddr)
})
}
Loading