Skip to content
Merged
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
22 changes: 13 additions & 9 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
PACKAGE=github.com/symbioticfi/relay
# Pinned: abigen output changes between releases, and CI verifies that
# `make generate` leaves no diff. Bump deliberately and commit the regenerated
# bindings in the same change.
ABIGEN_VERSION ?= v1.17.5
IMAGE_REPO ?= relay_sidecar
BUILD_TIME ?= $(shell date -u +%Y-%m-%dT%H:%M:%SZ)

Expand Down Expand Up @@ -108,50 +112,50 @@ e2e-test:

.PHONY: gen-abi
gen-abi:
go run github.com/ethereum/go-ethereum/cmd/abigen@latest \
go run github.com/ethereum/go-ethereum/cmd/abigen@$(ABIGEN_VERSION) \
--abi symbiotic/client/evm/abi/ValSetDriver.abi.json \
--type ValSetDriver \
--pkg gen \
--out symbiotic/client/evm/gen/valsetDriver.go
go run github.com/ethereum/go-ethereum/cmd/abigen@latest \
go run github.com/ethereum/go-ethereum/cmd/abigen@$(ABIGEN_VERSION) \
--abi symbiotic/client/evm/abi/Settlement.abi.json \
--type Settlement \
--pkg gen \
--out symbiotic/client/evm/gen/settlement.go
go run github.com/ethereum/go-ethereum/cmd/abigen@latest \
go run github.com/ethereum/go-ethereum/cmd/abigen@$(ABIGEN_VERSION) \
--abi symbiotic/client/evm/abi/KeyRegistry.abi.json \
--type KeyRegistry \
--pkg gen \
--out symbiotic/client/evm/gen/keyRegistry.go
go run github.com/ethereum/go-ethereum/cmd/abigen@latest \
go run github.com/ethereum/go-ethereum/cmd/abigen@$(ABIGEN_VERSION) \
--abi symbiotic/client/evm/abi/VotingPowerProvider.abi.json \
--type VotingPowerProvider \
--pkg gen \
--out symbiotic/client/evm/gen/votingPowerProvider.go
go run github.com/ethereum/go-ethereum/cmd/abigen@latest \
go run github.com/ethereum/go-ethereum/cmd/abigen@$(ABIGEN_VERSION) \
--abi symbiotic/client/evm/abi/OperatorRegistry.abi.json \
--type OperatorRegistry \
--pkg gen \
--out symbiotic/client/evm/gen/operatorRegistry.go

.PHONY: gen-abi-test
gen-abi-test:
go run github.com/ethereum/go-ethereum/cmd/abigen@latest \
go run github.com/ethereum/go-ethereum/cmd/abigen@$(ABIGEN_VERSION) \
--abi e2e/tests/evm/abi/MockERC20.abi.json \
--type MockERC20 \
--pkg gen \
--out e2e/tests/evm/gen/mockERC20.go
go run github.com/ethereum/go-ethereum/cmd/abigen@latest \
go run github.com/ethereum/go-ethereum/cmd/abigen@$(ABIGEN_VERSION) \
--abi e2e/tests/evm/abi/IOptInService.abi.json \
--type OptInService \
--pkg gen \
--out e2e/tests/evm/gen/optInService.go
go run github.com/ethereum/go-ethereum/cmd/abigen@latest \
go run github.com/ethereum/go-ethereum/cmd/abigen@$(ABIGEN_VERSION) \
--abi e2e/tests/evm/abi/OpNetVaultAutoDeployLogic.abi.json \
--type OpNetVaultAutoDeployLogic \
--pkg gen \
--out e2e/tests/evm/gen/opNetVaultAutoDeployLogic.go
go run github.com/ethereum/go-ethereum/cmd/abigen@latest \
go run github.com/ethereum/go-ethereum/cmd/abigen@$(ABIGEN_VERSION) \
--abi e2e/tests/evm/abi/Vault.abi.json \
--type Vault \
--pkg gen \
Expand Down
94 changes: 94 additions & 0 deletions internal/client/repository/codec/codec_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
package codec

import (
"context"
"math/big"
"testing"

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

symbiotic "github.com/symbioticfi/relay/symbiotic/entity"
)

// Committer indices serialize as a bitmap, so the round-trip returns the set in
// ascending order rather than any insertion order (see the deriver, which sorts
// them for exactly this reason). Slot rotation indexes into that slice, so a
// dropped or spurious member would hand a slot to the wrong validator.
func TestValidatorSetHeaderRoundTrip_PreservesCommitterSet(t *testing.T) {
t.Parallel()

const slotDuration = uint64(100)
keyTag := symbiotic.KeyTag(15)

publicKey1 := []byte("committer1_key")
publicKey2 := []byte("committer2_key")
publicKey3 := []byte("committer3_key")

valset := symbiotic.ValidatorSet{
RequiredKeyTag: keyTag,
CaptureTimestamp: 1000,
Validators: symbiotic.Validators{
{
Operator: common.HexToAddress("0x1111111111111111111111111111111111111111"),
VotingPower: symbiotic.ToVotingPower(big.NewInt(100)),
IsActive: true,
Keys: []symbiotic.ValidatorKey{
{Tag: keyTag, Payload: publicKey1},
},
},
{
Operator: common.HexToAddress("0x2222222222222222222222222222222222222222"),
VotingPower: symbiotic.ToVotingPower(big.NewInt(200)),
IsActive: true,
Keys: []symbiotic.ValidatorKey{
{Tag: keyTag, Payload: publicKey2},
},
},
{
Operator: common.HexToAddress("0x3333333333333333333333333333333333333333"),
VotingPower: symbiotic.ToVotingPower(big.NewInt(150)),
IsActive: true,
Keys: []symbiotic.ValidatorKey{
{Tag: keyTag, Payload: publicKey3},
},
},
},
// A strict subset, ascending, as the deriver emits it.
CommitterIndices: []uint32{0, 2},
}

require.True(
t,
valset.IsActiveCommitter(context.Background(), slotDuration, 1050, 0, publicKey1),
"sanity check: validator 0 should own the first slot before storage round-trip",
)

headerBytes, err := ValidatorSetHeaderToBytes(valset)
require.NoError(t, err)

_, committerIndices, err := ExtractAdditionalInfoFromHeaderData(headerBytes)
require.NoError(t, err)

assert.Equal(
t,
valset.CommitterIndices,
committerIndices,
"header round-trip should preserve the committer set in ascending order",
)

roundTripped := valset
roundTripped.CommitterIndices = committerIndices

assert.True(
t,
roundTripped.IsActiveCommitter(context.Background(), slotDuration, 1050, 0, publicKey1),
"the same validator should still own the first slot after header round-trip",
)
assert.False(
t,
roundTripped.IsActiveCommitter(context.Background(), slotDuration, 1050, 0, publicKey2),
"a non-committer should not gain a slot through the round-trip",
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
package entity_processor

import (
"math/big"
"testing"

"github.com/consensys/gnark-crypto/ecc/bn254"
"github.com/stretchr/testify/require"

symbiotic "github.com/symbioticfi/relay/symbiotic/entity"
"github.com/symbioticfi/relay/symbiotic/usecase/crypto"
"github.com/symbioticfi/relay/symbiotic/usecase/crypto/blsBn254"
)

// Validators are looked up by the on-chain G1 key, so a gossiped signature that
// keeps a victim's G1 but carries an attacker's G2 must not verify. Otherwise
// any peer could sign under any validator's identity, and the per-index dedup
// would then censor that validator's genuine signature.
func TestEntityProcessor_ProcessSignature_RejectsUnboundG2(t *testing.T) {
t.Parallel()

for name, newRepo := range backends() {
t.Run(name, func(t *testing.T) {
t.Parallel()

repo := newRepo(t)
epoch := symbiotic.Epoch(700)
req := randomSignatureRequest(t, epoch)

_, privateKeys := setupValidatorSetHeader(t, repo, epoch, big.NewInt(1000))

processor, err := NewEntityProcessor(Config{
Repo: repo,
Aggregator: createMockAggregator(t),
AggProofSignal: createMockAggProofSignal(t),
SignatureProcessedSignal: createMockSignatureProcessedSignal(t),
Metrics: doNothingMetrics{},
})
require.NoError(t, err)

// The attacker knows only the victim's public G1 identity.
victimPub := privateKeys[0][req.KeyTag].PublicKey()
victimG1Compressed := victimPub.Raw()[:32]

messageHash, err := crypto.HashMessage(req.KeyTag.Type(), req.Message)
require.NoError(t, err)

// Sign with an arbitrary scalar and publish the matching G2.
attackerScalar := big.NewInt(0xDEADBEEF)
_, _, _, g2Gen := bn254.Generators()
var attackerG2 bn254.G2Affine
attackerG2.ScalarMultiplication(&g2Gen, attackerScalar)

g1Hash, err := blsBn254.HashToG1(messageHash)
require.NoError(t, err)
var sig bn254.G1Affine
sig.ScalarMultiplication(g1Hash, attackerScalar)

attackerG2Compressed := attackerG2.Bytes()
forgedRaw := append(append([]byte{}, victimG1Compressed...), attackerG2Compressed[:]...)
forgedPub, err := crypto.NewPublicKey(symbiotic.KeyTypeBlsBn254, forgedRaw)
require.NoError(t, err)
require.Equal(t, victimPub.OnChain(), forgedPub.OnChain(),
"forgery resolves to the victim's on-chain identity")

forged := symbiotic.Signature{
KeyTag: req.KeyTag,
Epoch: epoch,
MessageHash: messageHash,
Signature: sig.Marshal(),
PublicKey: forgedPub,
}

require.Error(t, processor.ProcessSignature(t.Context(), forged, false),
"forged signature must not be accepted under the victim's identity")

_, err = repo.GetSignatureMap(t.Context(), forged.RequestID())
require.Error(t, err, "nothing recorded for the victim")

// The victim's genuine signature is still accepted afterwards.
genuine := signatureExtendedForRequest(t, privateKeys[0][req.KeyTag], req)
require.NoError(t, processor.ProcessSignature(t.Context(), genuine, false))
})
}
}
4 changes: 4 additions & 0 deletions symbiotic/client/evm/gen/keyRegistry.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions symbiotic/client/evm/gen/operatorRegistry.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions symbiotic/client/evm/gen/settlement.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions symbiotic/client/evm/gen/valsetDriver.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions symbiotic/client/evm/gen/votingPowerProvider.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading