From dde076d626a575ff810b51968211c1da1d57f876 Mon Sep 17 00:00:00 2001 From: samliok Date: Wed, 4 Jun 2025 21:17:36 -0700 Subject: [PATCH 01/16] add simplex pkg and bls structs --- simplex/bls.go | 127 ++++++++++++++++++++++++++++++++++++++++++ simplex/bls_test.go | 133 ++++++++++++++++++++++++++++++++++++++++++++ simplex/config.go | 33 +++++++++++ 3 files changed, 293 insertions(+) create mode 100644 simplex/bls.go create mode 100644 simplex/bls_test.go create mode 100644 simplex/config.go diff --git a/simplex/bls.go b/simplex/bls.go new file mode 100644 index 000000000000..7fa125d81434 --- /dev/null +++ b/simplex/bls.go @@ -0,0 +1,127 @@ +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package simplex + +import ( + "encoding/asn1" + "errors" + "fmt" + + "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/avalanchego/utils/crypto/bls" + "github.com/ava-labs/simplex" +) + +var ( + errSignatureVerificationFailed = errors.New("signature verification failed") + errSignerNotFound = errors.New("signer not found in the membership set") +) + +var _ simplex.Signer = (*BLSSigner)(nil) + +type SignFunc func(msg []byte) (*bls.Signature, error) + +// BLSSigner signs messages encoded with the provided ChainID and NetworkID +// using the SignBLS function. +type BLSSigner struct { + chainID ids.ID + subnetID ids.ID + // signBLS is passed in because we support both software and hardware BLS signing. + signBLS SignFunc + nodeID ids.NodeID +} + +type BLSVerifier struct { + nodeID2PK map[ids.NodeID]bls.PublicKey + subnetID ids.ID + chainID ids.ID +} + +func NewBLSAuth(config *Config) (BLSSigner, BLSVerifier) { + return BLSSigner{ + chainID: config.Ctx.ChainID, + subnetID: config.Ctx.SubnetID, + nodeID: config.Ctx.NodeID, + signBLS: config.SignBLS, + }, createVerifier(config) +} + +// Sign returns a signature on the given message using BLS signature scheme. +// It encodes the message to sign with the chain ID, and subnet ID, +func (s *BLSSigner) Sign(message []byte) ([]byte, error) { + message2Sign, err := encodeMessageToSign(message, s.chainID, s.subnetID) + if err != nil { + return nil, fmt.Errorf("failed to encode message to sign: %w", err) + } + + sig, err := s.signBLS(message2Sign) + if err != nil { + return nil, err + } + + sigBytes := bls.SignatureToBytes(sig) + return sigBytes, nil +} + +type encodedSimplexMessage struct { + Message []byte + ChainID []byte + SubnetID []byte +} + +// encodesMessageToSign returns a byte slice [simplexLabel][chainID][networkID][message length][message]. +func encodeMessageToSign(message []byte, chainID ids.ID, subnetID ids.ID) ([]byte, error) { + encodedSimplexMessage := encodedSimplexMessage{ + Message: message, + ChainID: chainID[:], + SubnetID: subnetID[:], + } + return asn1.Marshal(encodedSimplexMessage) +} + +func (v BLSVerifier) Verify(message []byte, signature []byte, signer simplex.NodeID) error { + if len(signer) != ids.NodeIDLen { + return fmt.Errorf("expected signer to be %d bytes but got %d bytes", ids.NodeIDLen, len(signer)) + } + + key := ids.NodeID(signer) + pk, exists := v.nodeID2PK[key] + if !exists { + return fmt.Errorf("%w: signer %x", errSignerNotFound, key) + } + + sig, err := bls.SignatureFromBytes(signature) + if err != nil { + return fmt.Errorf("failed to parse signature: %w", err) + } + + message2Verify, err := encodeMessageToSign(message, v.chainID, v.subnetID) + if err != nil { + return fmt.Errorf("failed to encode message to verify: %w", err) + } + + if !bls.Verify(&pk, sig, message2Verify) { + return errSignatureVerificationFailed + } + + return nil +} + +func createVerifier(config *Config) BLSVerifier { + verifier := BLSVerifier{ + nodeID2PK: make(map[ids.NodeID]bls.PublicKey), + subnetID: config.Ctx.SubnetID, + chainID: config.Ctx.ChainID, + } + + nodes := config.Validators.GetValidatorIDs(config.Ctx.SubnetID) + for _, node := range nodes { + validator, ok := config.Validators.GetValidator(config.Ctx.SubnetID, node) + if !ok { + continue + } + verifier.nodeID2PK[node] = *validator.PublicKey + } + return verifier +} diff --git a/simplex/bls_test.go b/simplex/bls_test.go new file mode 100644 index 000000000000..74dc3b5d0deb --- /dev/null +++ b/simplex/bls_test.go @@ -0,0 +1,133 @@ +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package simplex + +import ( + "testing" + + "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/avalanchego/snow/validators" + "github.com/ava-labs/avalanchego/utils/crypto/bls" + "github.com/ava-labs/avalanchego/utils/crypto/bls/signer/localsigner" + "github.com/stretchr/testify/require" +) + +var _ ValidatorInfo = (*testValidatorInfo)(nil) + +// testValidatorInfo is a mock implementation of ValidatorInfo for testing purposes. +// it assumes all validators are in the same subnet and returns all of them for any subnetID. +type testValidatorInfo struct { + validators map[ids.NodeID]validators.Validator +} + +func (v *testValidatorInfo) GetValidatorIDs(_ ids.ID) []ids.NodeID { + if v.validators == nil { + return nil + } + + ids := make([]ids.NodeID, 0, len(v.validators)) + for id := range v.validators { + ids = append(ids, id) + } + return ids +} + +func (v *testValidatorInfo) GetValidator(_ ids.ID, nodeID ids.NodeID) (*validators.Validator, bool) { + if v.validators == nil { + return nil, false + } + + val, exists := v.validators[nodeID] + if !exists { + return nil, false + } + return &val, true +} + +func newTestValidatorInfo(nodeIds []ids.NodeID, pks []*bls.PublicKey) *testValidatorInfo { + if len(nodeIds) != len(pks) { + panic("nodeIds and pks must have the same length") + } + + vds := make(map[ids.NodeID]validators.Validator, len(pks)) + for i, pk := range pks { + validator := validators.Validator{ + PublicKey: pk, + NodeID: nodeIds[i], + } + vds[nodeIds[i]] = validator + } + // all we need is to generate the public keys for the validators + return &testValidatorInfo{ + validators: vds, + } +} + +func newEngineConfig(ls *localsigner.LocalSigner) *Config { + nodeID := ids.GenerateTestNodeID() + + simplexChainContext := SimplexChainContext{ + NodeID: nodeID, + ChainID: ids.GenerateTestID(), + SubnetID: ids.GenerateTestID(), + } + + return &Config{ + Ctx: simplexChainContext, + Validators: newTestValidatorInfo([]ids.NodeID{nodeID}, []*bls.PublicKey{ls.PublicKey()}), + SignBLS: ls.Sign, + } +} + +func TestBLSSignVerify(t *testing.T) { + ls, err := localsigner.New() + require.NoError(t, err) + + config := newEngineConfig(ls) + + signer, verifier := NewBLSAuth(config) + + msg := "Begin at the beginning, and go on till you come to the end: then stop" + + sig, err := signer.Sign([]byte(msg)) + require.NoError(t, err) + + err = verifier.Verify([]byte(msg), sig, signer.nodeID[:]) + require.NoError(t, err) +} + +func TestSignerNotInMemberSet(t *testing.T) { + ls, err := localsigner.New() + require.NoError(t, err) + + config := newEngineConfig(ls) + signer, verifier := NewBLSAuth(config) + + msg := "Begin at the beginning, and go on till you come to the end: then stop" + + sig, err := signer.Sign([]byte(msg)) + require.NoError(t, err) + + notInMembershipSet := ids.GenerateTestNodeID() + err = verifier.Verify([]byte(msg), sig, notInMembershipSet[:]) + require.ErrorIs(t, err, errSignerNotFound) +} + +func TestSignerInvalidMessageEncoding(t *testing.T) { + ls, err := localsigner.New() + require.NoError(t, err) + + config := newEngineConfig(ls) + + // sign a message with invalid encoding + dummyMsg := []byte("dummy message") + sig, err := ls.Sign(dummyMsg) + require.NoError(t, err) + + sigBytes := bls.SignatureToBytes(sig) + + _, verifier := NewBLSAuth(config) + err = verifier.Verify(dummyMsg, sigBytes, config.Ctx.NodeID[:]) + require.ErrorIs(t, err, errSignatureVerificationFailed) +} diff --git a/simplex/config.go b/simplex/config.go new file mode 100644 index 000000000000..97e72e0f2efc --- /dev/null +++ b/simplex/config.go @@ -0,0 +1,33 @@ +// Copyright (C) 2019-2025, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package simplex + +import ( + "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/avalanchego/snow/validators" + "github.com/ava-labs/avalanchego/utils/logging" +) + +type ValidatorInfo interface { + GetValidatorIDs(subnetID ids.ID) []ids.NodeID + GetValidator(subnetID ids.ID, nodeID ids.NodeID) (*validators.Validator, bool) +} + +// Config wraps all the parameters needed for a simplex engine +type Config struct { + Ctx SimplexChainContext + Log logging.Logger + Validators ValidatorInfo + SignBLS SignFunc +} + +// Context is information about the current execution. +// [SubnitID] is the ID of the subnet this context exists within. +// [ChainID] is the ID of the chain this context exists within. +// [NodeID] is the ID of this node +type SimplexChainContext struct { + NodeID ids.NodeID + ChainID ids.ID + SubnetID ids.ID +} From c5be0a4459dd4c09f7d7c362a2b6052bc58c81fa Mon Sep 17 00:00:00 2001 From: samliok Date: Thu, 5 Jun 2025 10:26:54 -0700 Subject: [PATCH 02/16] add simplex to go mod + lint --- go.mod | 1 + go.sum | 2 ++ simplex/bls.go | 14 ++++++++------ simplex/bls_test.go | 5 +++-- simplex/config.go | 4 ++-- 5 files changed, 16 insertions(+), 10 deletions(-) diff --git a/go.mod b/go.mod index 1b43e438972c..8b9ffb89a875 100644 --- a/go.mod +++ b/go.mod @@ -85,6 +85,7 @@ require ( github.com/FactomProject/btcutilecc v0.0.0-20130527213604-d3a63a5752ec // indirect github.com/Microsoft/go-winio v0.6.1 // indirect github.com/VictoriaMetrics/fastcache v1.12.1 // indirect + github.com/ava-labs/simplex v0.0.0-20250605162940-db8e6d44f53d github.com/beorn7/perks v1.0.1 // indirect github.com/bits-and-blooms/bitset v1.10.0 // indirect github.com/btcsuite/btcd/btcec/v2 v2.3.2 // indirect diff --git a/go.sum b/go.sum index b686626c524e..cddaf16b2fd9 100644 --- a/go.sum +++ b/go.sum @@ -74,6 +74,8 @@ github.com/ava-labs/ledger-avalanche/go v0.0.0-20241009183145-e6f90a8a1a60 h1:EL github.com/ava-labs/ledger-avalanche/go v0.0.0-20241009183145-e6f90a8a1a60/go.mod h1:/7qKobTfbzBu7eSTVaXMTr56yTYk4j2Px6/8G+idxHo= github.com/ava-labs/libevm v1.13.14-0.2.0.release h1:uKGCc5/ceeBbfAPRVtBUxbQt50WzB2pEDb8Uy93ePgQ= github.com/ava-labs/libevm v1.13.14-0.2.0.release/go.mod h1:+Iol+sVQ1KyoBsHf3veyrBmHCXr3xXRWq6ZXkgVfNLU= +github.com/ava-labs/simplex v0.0.0-20250605162940-db8e6d44f53d h1:D/BOS3USdAigun2OP/6khvukKnn4BIKviYAdKLHN6zc= +github.com/ava-labs/simplex v0.0.0-20250605162940-db8e6d44f53d/go.mod h1:GVzumIo3zR23/qGRN2AdnVkIPHcKMq/D89EGWZfMGQ0= github.com/aymerick/raymond v2.0.3-0.20180322193309-b565731e1464+incompatible/go.mod h1:osfaiScAUVup+UC9Nfq76eWqDhXlp+4UYaA8uhTBO6g= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= diff --git a/simplex/bls.go b/simplex/bls.go index 7fa125d81434..f767dba21651 100644 --- a/simplex/bls.go +++ b/simplex/bls.go @@ -8,28 +8,29 @@ import ( "errors" "fmt" + "github.com/ava-labs/simplex" + "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/utils/crypto/bls" - "github.com/ava-labs/simplex" ) var ( errSignatureVerificationFailed = errors.New("signature verification failed") errSignerNotFound = errors.New("signer not found in the membership set") + simplexLabel = []byte("simplex") ) var _ simplex.Signer = (*BLSSigner)(nil) type SignFunc func(msg []byte) (*bls.Signature, error) -// BLSSigner signs messages encoded with the provided ChainID and NetworkID +// BLSSigner signs messages encoded with the provided ChainID and SubnetID. // using the SignBLS function. type BLSSigner struct { chainID ids.ID subnetID ids.ID // signBLS is passed in because we support both software and hardware BLS signing. signBLS SignFunc - nodeID ids.NodeID } type BLSVerifier struct { @@ -42,7 +43,6 @@ func NewBLSAuth(config *Config) (BLSSigner, BLSVerifier) { return BLSSigner{ chainID: config.Ctx.ChainID, subnetID: config.Ctx.SubnetID, - nodeID: config.Ctx.NodeID, signBLS: config.SignBLS, }, createVerifier(config) } @@ -64,18 +64,20 @@ func (s *BLSSigner) Sign(message []byte) ([]byte, error) { return sigBytes, nil } -type encodedSimplexMessage struct { +type encodedSimplexSignedPayload struct { Message []byte ChainID []byte SubnetID []byte + Label []byte } // encodesMessageToSign returns a byte slice [simplexLabel][chainID][networkID][message length][message]. func encodeMessageToSign(message []byte, chainID ids.ID, subnetID ids.ID) ([]byte, error) { - encodedSimplexMessage := encodedSimplexMessage{ + encodedSimplexMessage := encodedSimplexSignedPayload{ Message: message, ChainID: chainID[:], SubnetID: subnetID[:], + Label: simplexLabel, } return asn1.Marshal(encodedSimplexMessage) } diff --git a/simplex/bls_test.go b/simplex/bls_test.go index 74dc3b5d0deb..8797866e03c8 100644 --- a/simplex/bls_test.go +++ b/simplex/bls_test.go @@ -6,11 +6,12 @@ package simplex import ( "testing" + "github.com/stretchr/testify/require" + "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/snow/validators" "github.com/ava-labs/avalanchego/utils/crypto/bls" "github.com/ava-labs/avalanchego/utils/crypto/bls/signer/localsigner" - "github.com/stretchr/testify/require" ) var _ ValidatorInfo = (*testValidatorInfo)(nil) @@ -93,7 +94,7 @@ func TestBLSSignVerify(t *testing.T) { sig, err := signer.Sign([]byte(msg)) require.NoError(t, err) - err = verifier.Verify([]byte(msg), sig, signer.nodeID[:]) + err = verifier.Verify([]byte(msg), sig, config.Ctx.NodeID[:]) require.NoError(t, err) } diff --git a/simplex/config.go b/simplex/config.go index 97e72e0f2efc..8e16e26e3e99 100644 --- a/simplex/config.go +++ b/simplex/config.go @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2025, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package simplex @@ -23,7 +23,7 @@ type Config struct { } // Context is information about the current execution. -// [SubnitID] is the ID of the subnet this context exists within. +// [SubnetID] is the ID of the subnet this context exists within. // [ChainID] is the ID of the chain this context exists within. // [NodeID] is the ID of this node type SimplexChainContext struct { From 52d343ebca586d51ac348456d92b7dea903142b9 Mon Sep 17 00:00:00 2001 From: samliok Date: Thu, 5 Jun 2025 13:17:46 -0700 Subject: [PATCH 03/16] add log to createVerifier --- simplex/bls.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/simplex/bls.go b/simplex/bls.go index f767dba21651..e7b1ce25259c 100644 --- a/simplex/bls.go +++ b/simplex/bls.go @@ -9,6 +9,7 @@ import ( "fmt" "github.com/ava-labs/simplex" + "go.uber.org/zap" "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/utils/crypto/bls" @@ -121,6 +122,7 @@ func createVerifier(config *Config) BLSVerifier { for _, node := range nodes { validator, ok := config.Validators.GetValidator(config.Ctx.SubnetID, node) if !ok { + config.Log.Error("failed to get validator for node %s in subnet %s", zap.Stringer("node", node), zap.Stringer("subnetID", config.Ctx.SubnetID)) continue } verifier.nodeID2PK[node] = *validator.PublicKey From 458d540ea0cf1adbcb018d7fbbe7ac86eca19345 Mon Sep 17 00:00:00 2001 From: samliok Date: Mon, 9 Jun 2025 14:35:01 -0500 Subject: [PATCH 04/16] separate into utils file --- simplex/bls_test.go | 83 +++----------------------------------------- simplex/test_util.go | 83 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 87 insertions(+), 79 deletions(-) create mode 100644 simplex/test_util.go diff --git a/simplex/bls_test.go b/simplex/bls_test.go index 8797866e03c8..e5c59662b356 100644 --- a/simplex/bls_test.go +++ b/simplex/bls_test.go @@ -9,84 +9,13 @@ import ( "github.com/stretchr/testify/require" "github.com/ava-labs/avalanchego/ids" - "github.com/ava-labs/avalanchego/snow/validators" "github.com/ava-labs/avalanchego/utils/crypto/bls" - "github.com/ava-labs/avalanchego/utils/crypto/bls/signer/localsigner" ) -var _ ValidatorInfo = (*testValidatorInfo)(nil) - -// testValidatorInfo is a mock implementation of ValidatorInfo for testing purposes. -// it assumes all validators are in the same subnet and returns all of them for any subnetID. -type testValidatorInfo struct { - validators map[ids.NodeID]validators.Validator -} - -func (v *testValidatorInfo) GetValidatorIDs(_ ids.ID) []ids.NodeID { - if v.validators == nil { - return nil - } - - ids := make([]ids.NodeID, 0, len(v.validators)) - for id := range v.validators { - ids = append(ids, id) - } - return ids -} - -func (v *testValidatorInfo) GetValidator(_ ids.ID, nodeID ids.NodeID) (*validators.Validator, bool) { - if v.validators == nil { - return nil, false - } - - val, exists := v.validators[nodeID] - if !exists { - return nil, false - } - return &val, true -} - -func newTestValidatorInfo(nodeIds []ids.NodeID, pks []*bls.PublicKey) *testValidatorInfo { - if len(nodeIds) != len(pks) { - panic("nodeIds and pks must have the same length") - } - - vds := make(map[ids.NodeID]validators.Validator, len(pks)) - for i, pk := range pks { - validator := validators.Validator{ - PublicKey: pk, - NodeID: nodeIds[i], - } - vds[nodeIds[i]] = validator - } - // all we need is to generate the public keys for the validators - return &testValidatorInfo{ - validators: vds, - } -} - -func newEngineConfig(ls *localsigner.LocalSigner) *Config { - nodeID := ids.GenerateTestNodeID() - - simplexChainContext := SimplexChainContext{ - NodeID: nodeID, - ChainID: ids.GenerateTestID(), - SubnetID: ids.GenerateTestID(), - } - - return &Config{ - Ctx: simplexChainContext, - Validators: newTestValidatorInfo([]ids.NodeID{nodeID}, []*bls.PublicKey{ls.PublicKey()}), - SignBLS: ls.Sign, - } -} - func TestBLSSignVerify(t *testing.T) { - ls, err := localsigner.New() + config, err := newEngineConfig() require.NoError(t, err) - config := newEngineConfig(ls) - signer, verifier := NewBLSAuth(config) msg := "Begin at the beginning, and go on till you come to the end: then stop" @@ -99,10 +28,8 @@ func TestBLSSignVerify(t *testing.T) { } func TestSignerNotInMemberSet(t *testing.T) { - ls, err := localsigner.New() + config, err := newEngineConfig() require.NoError(t, err) - - config := newEngineConfig(ls) signer, verifier := NewBLSAuth(config) msg := "Begin at the beginning, and go on till you come to the end: then stop" @@ -116,14 +43,12 @@ func TestSignerNotInMemberSet(t *testing.T) { } func TestSignerInvalidMessageEncoding(t *testing.T) { - ls, err := localsigner.New() + config, err := newEngineConfig() require.NoError(t, err) - config := newEngineConfig(ls) - // sign a message with invalid encoding dummyMsg := []byte("dummy message") - sig, err := ls.Sign(dummyMsg) + sig, err := config.SignBLS(dummyMsg) require.NoError(t, err) sigBytes := bls.SignatureToBytes(sig) diff --git a/simplex/test_util.go b/simplex/test_util.go new file mode 100644 index 000000000000..e926fe686a4a --- /dev/null +++ b/simplex/test_util.go @@ -0,0 +1,83 @@ +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package simplex + +import ( + "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/avalanchego/snow/validators" + "github.com/ava-labs/avalanchego/utils/crypto/bls" + "github.com/ava-labs/avalanchego/utils/crypto/bls/signer/localsigner" +) + +var _ ValidatorInfo = (*testValidatorInfo)(nil) + +// testValidatorInfo is a mock implementation of ValidatorInfo for testing purposes. +// it assumes all validators are in the same subnet and returns all of them for any subnetID. +type testValidatorInfo struct { + validators map[ids.NodeID]validators.Validator +} + +func (v *testValidatorInfo) GetValidatorIDs(_ ids.ID) []ids.NodeID { + if v.validators == nil { + return nil + } + + ids := make([]ids.NodeID, 0, len(v.validators)) + for id := range v.validators { + ids = append(ids, id) + } + return ids +} + +func (v *testValidatorInfo) GetValidator(_ ids.ID, nodeID ids.NodeID) (*validators.Validator, bool) { + if v.validators == nil { + return nil, false + } + + val, exists := v.validators[nodeID] + if !exists { + return nil, false + } + return &val, true +} + +func newTestValidatorInfo(nodeIds []ids.NodeID, pks []*bls.PublicKey) *testValidatorInfo { + if len(nodeIds) != len(pks) { + panic("nodeIds and pks must have the same length") + } + + vds := make(map[ids.NodeID]validators.Validator, len(pks)) + for i, pk := range pks { + validator := validators.Validator{ + PublicKey: pk, + NodeID: nodeIds[i], + } + vds[nodeIds[i]] = validator + } + // all we need is to generate the public keys for the validators + return &testValidatorInfo{ + validators: vds, + } +} + +func newEngineConfig() (*Config, error) { + ls, err := localsigner.New() + if err != nil { + return nil, err + } + + nodeID := ids.GenerateTestNodeID() + + simplexChainContext := SimplexChainContext{ + NodeID: nodeID, + ChainID: ids.GenerateTestID(), + SubnetID: ids.GenerateTestID(), + } + + return &Config{ + Ctx: simplexChainContext, + Validators: newTestValidatorInfo([]ids.NodeID{nodeID}, []*bls.PublicKey{ls.PublicKey()}), + SignBLS: ls.Sign, + }, nil +} From 49aa0bbd9152938a00cd20543fd0c7bf0e89eb5f Mon Sep 17 00:00:00 2001 From: samliok Date: Tue, 10 Jun 2025 16:17:25 -0500 Subject: [PATCH 05/16] style improvements --- simplex/bls.go | 1 - simplex/test_util.go | 9 +-------- 2 files changed, 1 insertion(+), 9 deletions(-) diff --git a/simplex/bls.go b/simplex/bls.go index e7b1ce25259c..be9687e26288 100644 --- a/simplex/bls.go +++ b/simplex/bls.go @@ -72,7 +72,6 @@ type encodedSimplexSignedPayload struct { Label []byte } -// encodesMessageToSign returns a byte slice [simplexLabel][chainID][networkID][message length][message]. func encodeMessageToSign(message []byte, chainID ids.ID, subnetID ids.ID) ([]byte, error) { encodedSimplexMessage := encodedSimplexSignedPayload{ Message: message, diff --git a/simplex/test_util.go b/simplex/test_util.go index e926fe686a4a..7cbbcbaf2a2c 100644 --- a/simplex/test_util.go +++ b/simplex/test_util.go @@ -19,10 +19,6 @@ type testValidatorInfo struct { } func (v *testValidatorInfo) GetValidatorIDs(_ ids.ID) []ids.NodeID { - if v.validators == nil { - return nil - } - ids := make([]ids.NodeID, 0, len(v.validators)) for id := range v.validators { ids = append(ids, id) @@ -36,10 +32,7 @@ func (v *testValidatorInfo) GetValidator(_ ids.ID, nodeID ids.NodeID) (*validato } val, exists := v.validators[nodeID] - if !exists { - return nil, false - } - return &val, true + return &val, exists } func newTestValidatorInfo(nodeIds []ids.NodeID, pks []*bls.PublicKey) *testValidatorInfo { From 4dfdb34cf8e0de2ee4732850a210be415506e60e Mon Sep 17 00:00:00 2001 From: samliok Date: Tue, 10 Jun 2025 19:52:56 -0500 Subject: [PATCH 06/16] use GetValidatorSet for consistent validator set --- simplex/bls.go | 26 +++++++++++++++----------- simplex/bls_test.go | 9 ++++++--- simplex/config.go | 12 ++++++++++-- simplex/test_util.go | 29 +++++++++++------------------ 4 files changed, 42 insertions(+), 34 deletions(-) diff --git a/simplex/bls.go b/simplex/bls.go index be9687e26288..626c66d3937b 100644 --- a/simplex/bls.go +++ b/simplex/bls.go @@ -4,6 +4,7 @@ package simplex import ( + "context" "encoding/asn1" "errors" "fmt" @@ -40,12 +41,14 @@ type BLSVerifier struct { chainID ids.ID } -func NewBLSAuth(config *Config) (BLSSigner, BLSVerifier) { +func NewBLSAuth(config *Config) (BLSSigner, BLSVerifier, error) { + verifier, err := createVerifier(config) + return BLSSigner{ chainID: config.Ctx.ChainID, subnetID: config.Ctx.SubnetID, signBLS: config.SignBLS, - }, createVerifier(config) + }, verifier, err } // Sign returns a signature on the given message using BLS signature scheme. @@ -110,21 +113,22 @@ func (v BLSVerifier) Verify(message []byte, signature []byte, signer simplex.Nod return nil } -func createVerifier(config *Config) BLSVerifier { +func createVerifier(config *Config) (BLSVerifier, error) { verifier := BLSVerifier{ nodeID2PK: make(map[ids.NodeID]bls.PublicKey), subnetID: config.Ctx.SubnetID, chainID: config.Ctx.ChainID, } - nodes := config.Validators.GetValidatorIDs(config.Ctx.SubnetID) + nodes, err := config.Validators.GetValidatorSet(context.Background(), 0, config.Ctx.SubnetID) + if err != nil { + config.Log.Error("failed to get validator set", zap.Error(err), zap.Stringer("subnetID", config.Ctx.SubnetID)) + return BLSVerifier{}, err + } + for _, node := range nodes { - validator, ok := config.Validators.GetValidator(config.Ctx.SubnetID, node) - if !ok { - config.Log.Error("failed to get validator for node %s in subnet %s", zap.Stringer("node", node), zap.Stringer("subnetID", config.Ctx.SubnetID)) - continue - } - verifier.nodeID2PK[node] = *validator.PublicKey + verifier.nodeID2PK[node.NodeID] = *node.PublicKey } - return verifier + + return verifier, nil } diff --git a/simplex/bls_test.go b/simplex/bls_test.go index e5c59662b356..b08b36ab2da5 100644 --- a/simplex/bls_test.go +++ b/simplex/bls_test.go @@ -16,7 +16,8 @@ func TestBLSSignVerify(t *testing.T) { config, err := newEngineConfig() require.NoError(t, err) - signer, verifier := NewBLSAuth(config) + signer, verifier, err := NewBLSAuth(config) + require.NoError(t, err) msg := "Begin at the beginning, and go on till you come to the end: then stop" @@ -30,7 +31,8 @@ func TestBLSSignVerify(t *testing.T) { func TestSignerNotInMemberSet(t *testing.T) { config, err := newEngineConfig() require.NoError(t, err) - signer, verifier := NewBLSAuth(config) + signer, verifier, err := NewBLSAuth(config) + require.NoError(t, err) msg := "Begin at the beginning, and go on till you come to the end: then stop" @@ -53,7 +55,8 @@ func TestSignerInvalidMessageEncoding(t *testing.T) { sigBytes := bls.SignatureToBytes(sig) - _, verifier := NewBLSAuth(config) + _, verifier, err := NewBLSAuth(config) + require.NoError(t, err) err = verifier.Verify(dummyMsg, sigBytes, config.Ctx.NodeID[:]) require.ErrorIs(t, err, errSignatureVerificationFailed) } diff --git a/simplex/config.go b/simplex/config.go index 8e16e26e3e99..e778e408de12 100644 --- a/simplex/config.go +++ b/simplex/config.go @@ -4,14 +4,22 @@ package simplex import ( + "context" + "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/snow/validators" "github.com/ava-labs/avalanchego/utils/logging" ) type ValidatorInfo interface { - GetValidatorIDs(subnetID ids.ID) []ids.NodeID - GetValidator(subnetID ids.ID, nodeID ids.NodeID) (*validators.Validator, bool) + // GetValidatorSet returns the validators of the provided subnet at the + // requested P-chain height. + // The returned map should not be modified. + GetValidatorSet( + ctx context.Context, + height uint64, + subnetID ids.ID, + ) (map[ids.NodeID]*validators.GetValidatorOutput, error) } // Config wraps all the parameters needed for a simplex engine diff --git a/simplex/test_util.go b/simplex/test_util.go index 7cbbcbaf2a2c..2ac87c13ffd2 100644 --- a/simplex/test_util.go +++ b/simplex/test_util.go @@ -4,6 +4,8 @@ package simplex import ( + "context" + "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/snow/validators" "github.com/ava-labs/avalanchego/utils/crypto/bls" @@ -15,24 +17,15 @@ var _ ValidatorInfo = (*testValidatorInfo)(nil) // testValidatorInfo is a mock implementation of ValidatorInfo for testing purposes. // it assumes all validators are in the same subnet and returns all of them for any subnetID. type testValidatorInfo struct { - validators map[ids.NodeID]validators.Validator -} - -func (v *testValidatorInfo) GetValidatorIDs(_ ids.ID) []ids.NodeID { - ids := make([]ids.NodeID, 0, len(v.validators)) - for id := range v.validators { - ids = append(ids, id) - } - return ids + validators map[ids.NodeID]*validators.GetValidatorOutput } -func (v *testValidatorInfo) GetValidator(_ ids.ID, nodeID ids.NodeID) (*validators.Validator, bool) { - if v.validators == nil { - return nil, false - } - - val, exists := v.validators[nodeID] - return &val, exists +func (t *testValidatorInfo) GetValidatorSet( + context.Context, + uint64, + ids.ID, +) (map[ids.NodeID]*validators.GetValidatorOutput, error) { + return t.validators, nil } func newTestValidatorInfo(nodeIds []ids.NodeID, pks []*bls.PublicKey) *testValidatorInfo { @@ -40,9 +33,9 @@ func newTestValidatorInfo(nodeIds []ids.NodeID, pks []*bls.PublicKey) *testValid panic("nodeIds and pks must have the same length") } - vds := make(map[ids.NodeID]validators.Validator, len(pks)) + vds := make(map[ids.NodeID]*validators.GetValidatorOutput, len(pks)) for i, pk := range pks { - validator := validators.Validator{ + validator := &validators.GetValidatorOutput{ PublicKey: pk, NodeID: nodeIds[i], } From 8c78c03eb627fdb67dbee3d70ba74c70dc284a11 Mon Sep 17 00:00:00 2001 From: samliok Date: Wed, 11 Jun 2025 11:23:56 -0500 Subject: [PATCH 07/16] pass in membership set to config --- simplex/bls.go | 20 ++++++-------------- simplex/bls_test.go | 9 +++------ simplex/config.go | 27 ++++++++++----------------- simplex/test_util.go | 24 ++---------------------- 4 files changed, 21 insertions(+), 59 deletions(-) diff --git a/simplex/bls.go b/simplex/bls.go index 626c66d3937b..524c91842da8 100644 --- a/simplex/bls.go +++ b/simplex/bls.go @@ -4,13 +4,11 @@ package simplex import ( - "context" "encoding/asn1" "errors" "fmt" "github.com/ava-labs/simplex" - "go.uber.org/zap" "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/utils/crypto/bls" @@ -41,14 +39,14 @@ type BLSVerifier struct { chainID ids.ID } -func NewBLSAuth(config *Config) (BLSSigner, BLSVerifier, error) { - verifier, err := createVerifier(config) +func NewBLSAuth(config *Config) (BLSSigner, BLSVerifier) { + verifier := createVerifier(config) return BLSSigner{ chainID: config.Ctx.ChainID, subnetID: config.Ctx.SubnetID, signBLS: config.SignBLS, - }, verifier, err + }, verifier } // Sign returns a signature on the given message using BLS signature scheme. @@ -113,22 +111,16 @@ func (v BLSVerifier) Verify(message []byte, signature []byte, signer simplex.Nod return nil } -func createVerifier(config *Config) (BLSVerifier, error) { +func createVerifier(config *Config) BLSVerifier { verifier := BLSVerifier{ nodeID2PK: make(map[ids.NodeID]bls.PublicKey), subnetID: config.Ctx.SubnetID, chainID: config.Ctx.ChainID, } - nodes, err := config.Validators.GetValidatorSet(context.Background(), 0, config.Ctx.SubnetID) - if err != nil { - config.Log.Error("failed to get validator set", zap.Error(err), zap.Stringer("subnetID", config.Ctx.SubnetID)) - return BLSVerifier{}, err - } - - for _, node := range nodes { + for _, node := range config.Validators { verifier.nodeID2PK[node.NodeID] = *node.PublicKey } - return verifier, nil + return verifier } diff --git a/simplex/bls_test.go b/simplex/bls_test.go index b08b36ab2da5..e5c59662b356 100644 --- a/simplex/bls_test.go +++ b/simplex/bls_test.go @@ -16,8 +16,7 @@ func TestBLSSignVerify(t *testing.T) { config, err := newEngineConfig() require.NoError(t, err) - signer, verifier, err := NewBLSAuth(config) - require.NoError(t, err) + signer, verifier := NewBLSAuth(config) msg := "Begin at the beginning, and go on till you come to the end: then stop" @@ -31,8 +30,7 @@ func TestBLSSignVerify(t *testing.T) { func TestSignerNotInMemberSet(t *testing.T) { config, err := newEngineConfig() require.NoError(t, err) - signer, verifier, err := NewBLSAuth(config) - require.NoError(t, err) + signer, verifier := NewBLSAuth(config) msg := "Begin at the beginning, and go on till you come to the end: then stop" @@ -55,8 +53,7 @@ func TestSignerInvalidMessageEncoding(t *testing.T) { sigBytes := bls.SignatureToBytes(sig) - _, verifier, err := NewBLSAuth(config) - require.NoError(t, err) + _, verifier := NewBLSAuth(config) err = verifier.Verify(dummyMsg, sigBytes, config.Ctx.NodeID[:]) require.ErrorIs(t, err, errSignatureVerificationFailed) } diff --git a/simplex/config.go b/simplex/config.go index e778e408de12..ecd792eb3646 100644 --- a/simplex/config.go +++ b/simplex/config.go @@ -4,30 +4,23 @@ package simplex import ( - "context" - "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/snow/validators" "github.com/ava-labs/avalanchego/utils/logging" ) -type ValidatorInfo interface { - // GetValidatorSet returns the validators of the provided subnet at the - // requested P-chain height. - // The returned map should not be modified. - GetValidatorSet( - ctx context.Context, - height uint64, - subnetID ids.ID, - ) (map[ids.NodeID]*validators.GetValidatorOutput, error) -} - // Config wraps all the parameters needed for a simplex engine type Config struct { - Ctx SimplexChainContext - Log logging.Logger - Validators ValidatorInfo - SignBLS SignFunc + Ctx SimplexChainContext + Log logging.Logger + + // Validators is a map of node IDs to their validator information. + // This tells the node about the current membership set, and should be consistent + // across all nodes in the subnet. + Validators map[ids.NodeID]*validators.GetValidatorOutput + + // SignBLS is the signing function used for this node to sign messages. + SignBLS SignFunc } // Context is information about the current execution. diff --git a/simplex/test_util.go b/simplex/test_util.go index 2ac87c13ffd2..86e0c4323045 100644 --- a/simplex/test_util.go +++ b/simplex/test_util.go @@ -4,31 +4,13 @@ package simplex import ( - "context" - "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/snow/validators" "github.com/ava-labs/avalanchego/utils/crypto/bls" "github.com/ava-labs/avalanchego/utils/crypto/bls/signer/localsigner" ) -var _ ValidatorInfo = (*testValidatorInfo)(nil) - -// testValidatorInfo is a mock implementation of ValidatorInfo for testing purposes. -// it assumes all validators are in the same subnet and returns all of them for any subnetID. -type testValidatorInfo struct { - validators map[ids.NodeID]*validators.GetValidatorOutput -} - -func (t *testValidatorInfo) GetValidatorSet( - context.Context, - uint64, - ids.ID, -) (map[ids.NodeID]*validators.GetValidatorOutput, error) { - return t.validators, nil -} - -func newTestValidatorInfo(nodeIds []ids.NodeID, pks []*bls.PublicKey) *testValidatorInfo { +func newTestValidatorInfo(nodeIds []ids.NodeID, pks []*bls.PublicKey) map[ids.NodeID]*validators.GetValidatorOutput { if len(nodeIds) != len(pks) { panic("nodeIds and pks must have the same length") } @@ -42,9 +24,7 @@ func newTestValidatorInfo(nodeIds []ids.NodeID, pks []*bls.PublicKey) *testValid vds[nodeIds[i]] = validator } // all we need is to generate the public keys for the validators - return &testValidatorInfo{ - validators: vds, - } + return vds } func newEngineConfig() (*Config, error) { From 14ad42faaa6b73395d4ceb651711efe0f2393a51 Mon Sep 17 00:00:00 2001 From: samliok Date: Wed, 11 Jun 2025 11:31:19 -0500 Subject: [PATCH 08/16] remove comment --- simplex/test_util.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/simplex/test_util.go b/simplex/test_util.go index 86e0c4323045..9541a6d2b2dd 100644 --- a/simplex/test_util.go +++ b/simplex/test_util.go @@ -23,7 +23,7 @@ func newTestValidatorInfo(nodeIds []ids.NodeID, pks []*bls.PublicKey) map[ids.No } vds[nodeIds[i]] = validator } - // all we need is to generate the public keys for the validators + return vds } From ed4422b956188ebd520aa00fcf447586551c6766 Mon Sep 17 00:00:00 2001 From: samliok Date: Thu, 12 Jun 2025 14:03:38 -0500 Subject: [PATCH 09/16] test_util simplified --- simplex/test_util.go | 24 ++++++++++-------------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/simplex/test_util.go b/simplex/test_util.go index 9541a6d2b2dd..0e8a90378eb6 100644 --- a/simplex/test_util.go +++ b/simplex/test_util.go @@ -6,22 +6,13 @@ package simplex import ( "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/snow/validators" - "github.com/ava-labs/avalanchego/utils/crypto/bls" "github.com/ava-labs/avalanchego/utils/crypto/bls/signer/localsigner" ) -func newTestValidatorInfo(nodeIds []ids.NodeID, pks []*bls.PublicKey) map[ids.NodeID]*validators.GetValidatorOutput { - if len(nodeIds) != len(pks) { - panic("nodeIds and pks must have the same length") - } - - vds := make(map[ids.NodeID]*validators.GetValidatorOutput, len(pks)) - for i, pk := range pks { - validator := &validators.GetValidatorOutput{ - PublicKey: pk, - NodeID: nodeIds[i], - } - vds[nodeIds[i]] = validator +func newTestValidatorInfo(allVds []validators.GetValidatorOutput) map[ids.NodeID]*validators.GetValidatorOutput { + vds := make(map[ids.NodeID]*validators.GetValidatorOutput, len(allVds)) + for _, vd := range allVds { + vds[vd.NodeID] = &vd } return vds @@ -41,9 +32,14 @@ func newEngineConfig() (*Config, error) { SubnetID: ids.GenerateTestID(), } + nodeInfo := validators.GetValidatorOutput{ + NodeID: nodeID, + PublicKey: ls.PublicKey(), + } + return &Config{ Ctx: simplexChainContext, - Validators: newTestValidatorInfo([]ids.NodeID{nodeID}, []*bls.PublicKey{ls.PublicKey()}), + Validators: newTestValidatorInfo([]validators.GetValidatorOutput{nodeInfo}), SignBLS: ls.Sign, }, nil } From 88b06d5958dc29584ddacfd5bc4462c759b0c600 Mon Sep 17 00:00:00 2001 From: samliok Date: Thu, 12 Jun 2025 14:38:28 -0500 Subject: [PATCH 10/16] go get --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 62b5e5bc8b69..18c41928d458 100644 --- a/go.mod +++ b/go.mod @@ -84,7 +84,7 @@ require ( github.com/FactomProject/btcutilecc v0.0.0-20130527213604-d3a63a5752ec // indirect github.com/Microsoft/go-winio v0.6.1 // indirect github.com/VictoriaMetrics/fastcache v1.12.1 // indirect - github.com/ava-labs/simplex v0.0.0-20250605162940-db8e6d44f53d + github.com/ava-labs/simplex v0.0.0-20250611154800-78b82e9820e5 github.com/beorn7/perks v1.0.1 // indirect github.com/bits-and-blooms/bitset v1.10.0 // indirect github.com/btcsuite/btcd/btcec/v2 v2.3.2 // indirect diff --git a/go.sum b/go.sum index 265cffc1dda4..5eedbfcb4301 100644 --- a/go.sum +++ b/go.sum @@ -70,10 +70,10 @@ github.com/ava-labs/coreth v0.15.2-rc.0.0.20250610170140-2fcf45f828a2 h1:/E1w2S6 github.com/ava-labs/coreth v0.15.2-rc.0.0.20250610170140-2fcf45f828a2/go.mod h1:cqwBag+zzqifDutdPVzZKovfC2d0L8Zxq4YgTGaMCwg= github.com/ava-labs/ledger-avalanche/go v0.0.0-20241009183145-e6f90a8a1a60 h1:EL66gtXOAwR/4KYBjOV03LTWgkEXvLePribLlJNu4g0= github.com/ava-labs/ledger-avalanche/go v0.0.0-20241009183145-e6f90a8a1a60/go.mod h1:/7qKobTfbzBu7eSTVaXMTr56yTYk4j2Px6/8G+idxHo= -github.com/ava-labs/simplex v0.0.0-20250605162940-db8e6d44f53d h1:D/BOS3USdAigun2OP/6khvukKnn4BIKviYAdKLHN6zc= -github.com/ava-labs/simplex v0.0.0-20250605162940-db8e6d44f53d/go.mod h1:GVzumIo3zR23/qGRN2AdnVkIPHcKMq/D89EGWZfMGQ0= github.com/ava-labs/libevm v0.0.0-20250610142802-2672fbd7cdfc h1:cSXaUY4hdmoJ2FJOgOzn+WiovN/ZB/zkNRgnZhE50OA= github.com/ava-labs/libevm v0.0.0-20250610142802-2672fbd7cdfc/go.mod h1:+Iol+sVQ1KyoBsHf3veyrBmHCXr3xXRWq6ZXkgVfNLU= +github.com/ava-labs/simplex v0.0.0-20250611154800-78b82e9820e5 h1:rwPm63i5nJ2XIuNjO2H68gDmMKje0VW7orLZMISPrC8= +github.com/ava-labs/simplex v0.0.0-20250611154800-78b82e9820e5/go.mod h1:GVzumIo3zR23/qGRN2AdnVkIPHcKMq/D89EGWZfMGQ0= github.com/aymerick/raymond v2.0.3-0.20180322193309-b565731e1464+incompatible/go.mod h1:osfaiScAUVup+UC9Nfq76eWqDhXlp+4UYaA8uhTBO6g= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= From f3410c3a855b043a08586ca3635230c0338c41db Mon Sep 17 00:00:00 2001 From: samliok Date: Fri, 13 Jun 2025 16:00:25 -0500 Subject: [PATCH 11/16] add codec.Manager --- simplex/bls.go | 11 +++++------ simplex/codec.go | 26 ++++++++++++++++++++++++++ 2 files changed, 31 insertions(+), 6 deletions(-) create mode 100644 simplex/codec.go diff --git a/simplex/bls.go b/simplex/bls.go index 524c91842da8..4a82e856e27e 100644 --- a/simplex/bls.go +++ b/simplex/bls.go @@ -4,7 +4,6 @@ package simplex import ( - "encoding/asn1" "errors" "fmt" @@ -67,10 +66,10 @@ func (s *BLSSigner) Sign(message []byte) ([]byte, error) { } type encodedSimplexSignedPayload struct { - Message []byte - ChainID []byte - SubnetID []byte - Label []byte + Message []byte `serialize:"true"` + ChainID []byte `serialize:"true"` + SubnetID []byte `serialize:"true"` + Label []byte `serialize:"true"` } func encodeMessageToSign(message []byte, chainID ids.ID, subnetID ids.ID) ([]byte, error) { @@ -80,7 +79,7 @@ func encodeMessageToSign(message []byte, chainID ids.ID, subnetID ids.ID) ([]byt SubnetID: subnetID[:], Label: simplexLabel, } - return asn1.Marshal(encodedSimplexMessage) + return Codec.Marshal(CodecVersion, &encodedSimplexMessage) } func (v BLSVerifier) Verify(message []byte, signature []byte, signer simplex.NodeID) error { diff --git a/simplex/codec.go b/simplex/codec.go new file mode 100644 index 000000000000..a79200ee4474 --- /dev/null +++ b/simplex/codec.go @@ -0,0 +1,26 @@ +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package simplex + +import ( + "math" + + "github.com/ava-labs/avalanchego/codec" + "github.com/ava-labs/avalanchego/codec/linearcodec" + "github.com/ava-labs/avalanchego/vms/platformvm/warp" +) + +const CodecVersion = warp.CodecVersion + 1 + +var Codec codec.Manager + +func init() { + lc := linearcodec.NewDefault() + + Codec = codec.NewManager(math.MaxInt) + + if err := Codec.RegisterCodec(CodecVersion, lc); err != nil { + panic(err) + } +} From 3e86cd5e7b6acf3afc57859a7950e3163cb571c9 Mon Sep 17 00:00:00 2001 From: samliok Date: Mon, 16 Jun 2025 14:48:50 -0500 Subject: [PATCH 12/16] use network id over subnet id --- simplex/bls.go | 49 ++++++++++++++++++++++---------------------- simplex/config.go | 6 +++--- simplex/test_util.go | 7 ++++--- 3 files changed, 32 insertions(+), 30 deletions(-) diff --git a/simplex/bls.go b/simplex/bls.go index 4a82e856e27e..014c47571d7f 100644 --- a/simplex/bls.go +++ b/simplex/bls.go @@ -16,7 +16,6 @@ import ( var ( errSignatureVerificationFailed = errors.New("signature verification failed") errSignerNotFound = errors.New("signer not found in the membership set") - simplexLabel = []byte("simplex") ) var _ simplex.Signer = (*BLSSigner)(nil) @@ -26,15 +25,15 @@ type SignFunc func(msg []byte) (*bls.Signature, error) // BLSSigner signs messages encoded with the provided ChainID and SubnetID. // using the SignBLS function. type BLSSigner struct { - chainID ids.ID - subnetID ids.ID + chainID ids.ID + networkID uint32 // signBLS is passed in because we support both software and hardware BLS signing. signBLS SignFunc } type BLSVerifier struct { - nodeID2PK map[ids.NodeID]bls.PublicKey - subnetID ids.ID + nodeID2PK map[ids.NodeID]*bls.PublicKey + networkID uint32 chainID ids.ID } @@ -42,16 +41,16 @@ func NewBLSAuth(config *Config) (BLSSigner, BLSVerifier) { verifier := createVerifier(config) return BLSSigner{ - chainID: config.Ctx.ChainID, - subnetID: config.Ctx.SubnetID, - signBLS: config.SignBLS, + chainID: config.Ctx.ChainID, + networkID: config.Ctx.NetworkID, + signBLS: config.SignBLS, }, verifier } // Sign returns a signature on the given message using BLS signature scheme. // It encodes the message to sign with the chain ID, and subnet ID, func (s *BLSSigner) Sign(message []byte) ([]byte, error) { - message2Sign, err := encodeMessageToSign(message, s.chainID, s.subnetID) + message2Sign, err := encodeMessageToSign(message, s.chainID, s.networkID) if err != nil { return nil, fmt.Errorf("failed to encode message to sign: %w", err) } @@ -66,18 +65,16 @@ func (s *BLSSigner) Sign(message []byte) ([]byte, error) { } type encodedSimplexSignedPayload struct { - Message []byte `serialize:"true"` - ChainID []byte `serialize:"true"` - SubnetID []byte `serialize:"true"` - Label []byte `serialize:"true"` + NewtorkID uint32 `serialize:"true"` + ChainID ids.ID `serialize:"true"` + Message []byte `serialize:"true"` } -func encodeMessageToSign(message []byte, chainID ids.ID, subnetID ids.ID) ([]byte, error) { +func encodeMessageToSign(message []byte, chainID ids.ID, networkID uint32) ([]byte, error) { encodedSimplexMessage := encodedSimplexSignedPayload{ - Message: message, - ChainID: chainID[:], - SubnetID: subnetID[:], - Label: simplexLabel, + Message: message, + ChainID: chainID, + NewtorkID: networkID, } return Codec.Marshal(CodecVersion, &encodedSimplexMessage) } @@ -87,7 +84,11 @@ func (v BLSVerifier) Verify(message []byte, signature []byte, signer simplex.Nod return fmt.Errorf("expected signer to be %d bytes but got %d bytes", ids.NodeIDLen, len(signer)) } - key := ids.NodeID(signer) + key, err := ids.ToNodeID(signer) + if err != nil { + return fmt.Errorf("failed to convert signer to node ID: %w", err) + } + pk, exists := v.nodeID2PK[key] if !exists { return fmt.Errorf("%w: signer %x", errSignerNotFound, key) @@ -98,12 +99,12 @@ func (v BLSVerifier) Verify(message []byte, signature []byte, signer simplex.Nod return fmt.Errorf("failed to parse signature: %w", err) } - message2Verify, err := encodeMessageToSign(message, v.chainID, v.subnetID) + message2Verify, err := encodeMessageToSign(message, v.chainID, v.networkID) if err != nil { return fmt.Errorf("failed to encode message to verify: %w", err) } - if !bls.Verify(&pk, sig, message2Verify) { + if !bls.Verify(pk, sig, message2Verify) { return errSignatureVerificationFailed } @@ -112,13 +113,13 @@ func (v BLSVerifier) Verify(message []byte, signature []byte, signer simplex.Nod func createVerifier(config *Config) BLSVerifier { verifier := BLSVerifier{ - nodeID2PK: make(map[ids.NodeID]bls.PublicKey), - subnetID: config.Ctx.SubnetID, + nodeID2PK: make(map[ids.NodeID]*bls.PublicKey), + networkID: config.Ctx.NetworkID, chainID: config.Ctx.ChainID, } for _, node := range config.Validators { - verifier.nodeID2PK[node.NodeID] = *node.PublicKey + verifier.nodeID2PK[node.NodeID] = node.PublicKey } return verifier diff --git a/simplex/config.go b/simplex/config.go index ecd792eb3646..eb46e0bee7e3 100644 --- a/simplex/config.go +++ b/simplex/config.go @@ -28,7 +28,7 @@ type Config struct { // [ChainID] is the ID of the chain this context exists within. // [NodeID] is the ID of this node type SimplexChainContext struct { - NodeID ids.NodeID - ChainID ids.ID - SubnetID ids.ID + NodeID ids.NodeID + ChainID ids.ID + NetworkID uint32 } diff --git a/simplex/test_util.go b/simplex/test_util.go index 0e8a90378eb6..545357d0e9fb 100644 --- a/simplex/test_util.go +++ b/simplex/test_util.go @@ -6,6 +6,7 @@ package simplex import ( "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/snow/validators" + "github.com/ava-labs/avalanchego/utils/constants" "github.com/ava-labs/avalanchego/utils/crypto/bls/signer/localsigner" ) @@ -27,9 +28,9 @@ func newEngineConfig() (*Config, error) { nodeID := ids.GenerateTestNodeID() simplexChainContext := SimplexChainContext{ - NodeID: nodeID, - ChainID: ids.GenerateTestID(), - SubnetID: ids.GenerateTestID(), + NodeID: nodeID, + ChainID: ids.GenerateTestID(), + NetworkID: constants.LocalID, } nodeInfo := validators.GetValidatorOutput{ From 317c9b5dae72d951c88b519469a0627515f593c5 Mon Sep 17 00:00:00 2001 From: samliok Date: Mon, 16 Jun 2025 16:00:50 -0500 Subject: [PATCH 13/16] add table tests for bls verifier --- simplex/bls.go | 8 +++- simplex/bls_test.go | 111 ++++++++++++++++++++++++++++---------------- 2 files changed, 76 insertions(+), 43 deletions(-) diff --git a/simplex/bls.go b/simplex/bls.go index 014c47571d7f..2dd805179daa 100644 --- a/simplex/bls.go +++ b/simplex/bls.go @@ -16,6 +16,8 @@ import ( var ( errSignatureVerificationFailed = errors.New("signature verification failed") errSignerNotFound = errors.New("signer not found in the membership set") + errInvalidNodeIDLength = errors.New("invalid node ID length") + errFailedToParseSignature = errors.New("failed to parse signature") ) var _ simplex.Signer = (*BLSSigner)(nil) @@ -81,9 +83,11 @@ func encodeMessageToSign(message []byte, chainID ids.ID, networkID uint32) ([]by func (v BLSVerifier) Verify(message []byte, signature []byte, signer simplex.NodeID) error { if len(signer) != ids.NodeIDLen { - return fmt.Errorf("expected signer to be %d bytes but got %d bytes", ids.NodeIDLen, len(signer)) + return fmt.Errorf("%w: expected signer to be %d bytes but got %d bytes", errInvalidNodeIDLength, ids.NodeIDLen, len(signer)) } + fmt.Println("verifying signature for signer", len(signer)) + key, err := ids.ToNodeID(signer) if err != nil { return fmt.Errorf("failed to convert signer to node ID: %w", err) @@ -96,7 +100,7 @@ func (v BLSVerifier) Verify(message []byte, signature []byte, signer simplex.Nod sig, err := bls.SignatureFromBytes(signature) if err != nil { - return fmt.Errorf("failed to parse signature: %w", err) + return fmt.Errorf("%w: %w", errFailedToParseSignature, err) } message2Verify, err := encodeMessageToSign(message, v.chainID, v.networkID) diff --git a/simplex/bls_test.go b/simplex/bls_test.go index e5c59662b356..7a047f1ff331 100644 --- a/simplex/bls_test.go +++ b/simplex/bls_test.go @@ -12,48 +12,77 @@ import ( "github.com/ava-labs/avalanchego/utils/crypto/bls" ) -func TestBLSSignVerify(t *testing.T) { - config, err := newEngineConfig() - require.NoError(t, err) - - signer, verifier := NewBLSAuth(config) - - msg := "Begin at the beginning, and go on till you come to the end: then stop" - - sig, err := signer.Sign([]byte(msg)) - require.NoError(t, err) - - err = verifier.Verify([]byte(msg), sig, config.Ctx.NodeID[:]) - require.NoError(t, err) -} - -func TestSignerNotInMemberSet(t *testing.T) { +func TestBLSVerifier(t *testing.T) { config, err := newEngineConfig() require.NoError(t, err) signer, verifier := NewBLSAuth(config) - - msg := "Begin at the beginning, and go on till you come to the end: then stop" - - sig, err := signer.Sign([]byte(msg)) - require.NoError(t, err) - - notInMembershipSet := ids.GenerateTestNodeID() - err = verifier.Verify([]byte(msg), sig, notInMembershipSet[:]) - require.ErrorIs(t, err, errSignerNotFound) -} - -func TestSignerInvalidMessageEncoding(t *testing.T) { - config, err := newEngineConfig() - require.NoError(t, err) - - // sign a message with invalid encoding - dummyMsg := []byte("dummy message") - sig, err := config.SignBLS(dummyMsg) - require.NoError(t, err) - - sigBytes := bls.SignatureToBytes(sig) - - _, verifier := NewBLSAuth(config) - err = verifier.Verify(dummyMsg, sigBytes, config.Ctx.NodeID[:]) - require.ErrorIs(t, err, errSignatureVerificationFailed) + otherNodeID := ids.GenerateTestNodeID() + + msg := []byte("Begin at the beginning, and go on till you come to the end: then stop") + tests := []struct { + name string + expectErr error + nodeID []byte + sig []byte + }{ + { + name: "valid signature", + expectErr: nil, + nodeID: config.Ctx.NodeID[:], + sig: func(msg []byte) []byte { + sig, err := signer.Sign(msg) + require.NoError(t, err) + return sig + }([]byte(msg)), + }, + { + name: "not in membership set", + expectErr: errSignerNotFound, + nodeID: otherNodeID[:], + sig: func() []byte { + sig, err := signer.Sign(msg) + require.NoError(t, err) + return sig + }(), + }, + { + name: "invalid encoding", + expectErr: errSignatureVerificationFailed, + nodeID: config.Ctx.NodeID[:], + sig: func() []byte { + sig, err := config.SignBLS(msg) + require.NoError(t, err) + return bls.SignatureToBytes(sig) + }(), + }, + { + name: "nodeID incorrect length", + expectErr: errInvalidNodeIDLength, + nodeID: []byte{0x01, 0x02, 0x03, 0x04, 0x05}, // Incorrect length NodeID + sig: func() []byte { + sig, err := signer.Sign(msg) + require.NoError(t, err) + return sig + }(), + }, + { + name: "nil signature", + expectErr: errFailedToParseSignature, + nodeID: config.Ctx.NodeID[:], + sig: nil, + }, + { + name: "malformed signature", + expectErr: errFailedToParseSignature, + nodeID: config.Ctx.NodeID[:], + sig: []byte{0x01, 0x02, 0x03}, // Malformed signature + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err = verifier.Verify(msg, tt.sig, tt.nodeID) + require.ErrorIs(t, err, tt.expectErr) + }) + } } From 65962974d6ea2fb946d884cfcb79c497dd4e1318 Mon Sep 17 00:00:00 2001 From: samliok Date: Mon, 16 Jun 2025 16:02:36 -0500 Subject: [PATCH 14/16] lint --- simplex/bls_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/simplex/bls_test.go b/simplex/bls_test.go index 7a047f1ff331..5dbea4a5887a 100644 --- a/simplex/bls_test.go +++ b/simplex/bls_test.go @@ -29,11 +29,11 @@ func TestBLSVerifier(t *testing.T) { name: "valid signature", expectErr: nil, nodeID: config.Ctx.NodeID[:], - sig: func(msg []byte) []byte { + sig: func() []byte { sig, err := signer.Sign(msg) require.NoError(t, err) return sig - }([]byte(msg)), + }(), }, { name: "not in membership set", From 5892918f127b1c2da09a9a5686c7ab70322d9175 Mon Sep 17 00:00:00 2001 From: samliok Date: Tue, 17 Jun 2025 11:53:02 -0500 Subject: [PATCH 15/16] testing cleanup, comment fixes, remove println --- simplex/bls.go | 14 ++++---------- simplex/bls_test.go | 14 +++++++------- simplex/config.go | 2 +- simplex/test_util.go | 2 +- 4 files changed, 13 insertions(+), 19 deletions(-) diff --git a/simplex/bls.go b/simplex/bls.go index 2dd805179daa..7530ea55bf4e 100644 --- a/simplex/bls.go +++ b/simplex/bls.go @@ -16,7 +16,7 @@ import ( var ( errSignatureVerificationFailed = errors.New("signature verification failed") errSignerNotFound = errors.New("signer not found in the membership set") - errInvalidNodeIDLength = errors.New("invalid node ID length") + errInvalidNodeID = errors.New("unable to parse node ID") errFailedToParseSignature = errors.New("failed to parse signature") ) @@ -24,7 +24,7 @@ var _ simplex.Signer = (*BLSSigner)(nil) type SignFunc func(msg []byte) (*bls.Signature, error) -// BLSSigner signs messages encoded with the provided ChainID and SubnetID. +// BLSSigner signs messages encoded with the provided ChainID and NetworkID. // using the SignBLS function. type BLSSigner struct { chainID ids.ID @@ -50,7 +50,7 @@ func NewBLSAuth(config *Config) (BLSSigner, BLSVerifier) { } // Sign returns a signature on the given message using BLS signature scheme. -// It encodes the message to sign with the chain ID, and subnet ID, +// It encodes the message to sign with the chain ID, and network ID, func (s *BLSSigner) Sign(message []byte) ([]byte, error) { message2Sign, err := encodeMessageToSign(message, s.chainID, s.networkID) if err != nil { @@ -82,15 +82,9 @@ func encodeMessageToSign(message []byte, chainID ids.ID, networkID uint32) ([]by } func (v BLSVerifier) Verify(message []byte, signature []byte, signer simplex.NodeID) error { - if len(signer) != ids.NodeIDLen { - return fmt.Errorf("%w: expected signer to be %d bytes but got %d bytes", errInvalidNodeIDLength, ids.NodeIDLen, len(signer)) - } - - fmt.Println("verifying signature for signer", len(signer)) - key, err := ids.ToNodeID(signer) if err != nil { - return fmt.Errorf("failed to convert signer to node ID: %w", err) + return fmt.Errorf("%w: %w", errInvalidNodeID, err) } pk, exists := v.nodeID2PK[key] diff --git a/simplex/bls_test.go b/simplex/bls_test.go index 5dbea4a5887a..cc38dd6d918d 100644 --- a/simplex/bls_test.go +++ b/simplex/bls_test.go @@ -26,7 +26,7 @@ func TestBLSVerifier(t *testing.T) { sig []byte }{ { - name: "valid signature", + name: "valid_signature", expectErr: nil, nodeID: config.Ctx.NodeID[:], sig: func() []byte { @@ -36,7 +36,7 @@ func TestBLSVerifier(t *testing.T) { }(), }, { - name: "not in membership set", + name: "not_in_membership_set", expectErr: errSignerNotFound, nodeID: otherNodeID[:], sig: func() []byte { @@ -46,7 +46,7 @@ func TestBLSVerifier(t *testing.T) { }(), }, { - name: "invalid encoding", + name: "invalid_message_encoding", expectErr: errSignatureVerificationFailed, nodeID: config.Ctx.NodeID[:], sig: func() []byte { @@ -56,8 +56,8 @@ func TestBLSVerifier(t *testing.T) { }(), }, { - name: "nodeID incorrect length", - expectErr: errInvalidNodeIDLength, + name: "invalid_nodeID", + expectErr: errInvalidNodeID, nodeID: []byte{0x01, 0x02, 0x03, 0x04, 0x05}, // Incorrect length NodeID sig: func() []byte { sig, err := signer.Sign(msg) @@ -66,13 +66,13 @@ func TestBLSVerifier(t *testing.T) { }(), }, { - name: "nil signature", + name: "nil_signature", expectErr: errFailedToParseSignature, nodeID: config.Ctx.NodeID[:], sig: nil, }, { - name: "malformed signature", + name: "malformed_signature", expectErr: errFailedToParseSignature, nodeID: config.Ctx.NodeID[:], sig: []byte{0x01, 0x02, 0x03}, // Malformed signature diff --git a/simplex/config.go b/simplex/config.go index eb46e0bee7e3..62afd9758981 100644 --- a/simplex/config.go +++ b/simplex/config.go @@ -24,7 +24,7 @@ type Config struct { } // Context is information about the current execution. -// [SubnetID] is the ID of the subnet this context exists within. +// [Network] is the ID of the network this context exists within. // [ChainID] is the ID of the chain this context exists within. // [NodeID] is the ID of this node type SimplexChainContext struct { diff --git a/simplex/test_util.go b/simplex/test_util.go index 545357d0e9fb..5b167bd17e8d 100644 --- a/simplex/test_util.go +++ b/simplex/test_util.go @@ -30,7 +30,7 @@ func newEngineConfig() (*Config, error) { simplexChainContext := SimplexChainContext{ NodeID: nodeID, ChainID: ids.GenerateTestID(), - NetworkID: constants.LocalID, + NetworkID: constants.UnitTestID, } nodeInfo := validators.GetValidatorOutput{ From 9f5c59f7b4ee848ba484c04efd426da449290998 Mon Sep 17 00:00:00 2001 From: samliok Date: Tue, 17 Jun 2025 12:21:46 -0500 Subject: [PATCH 16/16] update docs of config struct --- simplex/config.go | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/simplex/config.go b/simplex/config.go index 62afd9758981..e95f080e2249 100644 --- a/simplex/config.go +++ b/simplex/config.go @@ -24,11 +24,13 @@ type Config struct { } // Context is information about the current execution. -// [Network] is the ID of the network this context exists within. -// [ChainID] is the ID of the chain this context exists within. -// [NodeID] is the ID of this node type SimplexChainContext struct { - NodeID ids.NodeID - ChainID ids.ID + // Network is the ID of the network this context exists within. + NodeID ids.NodeID + + // ChainID is the ID of the chain this context exists within. + ChainID ids.ID + + // NodeID is the ID of this node NetworkID uint32 }