Skip to content

Commit 0f05748

Browse files
authored
BLS Components for Simplex (#3993)
Signed-off-by: Sam Liokumovich <65994425+samliok@users.noreply.github.com>
1 parent 5a5dbaa commit 0f05748

File tree

7 files changed

+323
-0
lines changed

7 files changed

+323
-0
lines changed

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ require (
8484
github.com/FactomProject/btcutilecc v0.0.0-20130527213604-d3a63a5752ec // indirect
8585
github.com/Microsoft/go-winio v0.6.1 // indirect
8686
github.com/VictoriaMetrics/fastcache v1.12.1 // indirect
87+
github.com/ava-labs/simplex v0.0.0-20250611154800-78b82e9820e5
8788
github.com/beorn7/perks v1.0.1 // indirect
8889
github.com/bits-and-blooms/bitset v1.10.0 // indirect
8990
github.com/btcsuite/btcd/btcec/v2 v2.3.2 // indirect

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,8 @@ github.com/ava-labs/ledger-avalanche/go v0.0.0-20241009183145-e6f90a8a1a60 h1:EL
7272
github.com/ava-labs/ledger-avalanche/go v0.0.0-20241009183145-e6f90a8a1a60/go.mod h1:/7qKobTfbzBu7eSTVaXMTr56yTYk4j2Px6/8G+idxHo=
7373
github.com/ava-labs/libevm v0.0.0-20250610142802-2672fbd7cdfc h1:cSXaUY4hdmoJ2FJOgOzn+WiovN/ZB/zkNRgnZhE50OA=
7474
github.com/ava-labs/libevm v0.0.0-20250610142802-2672fbd7cdfc/go.mod h1:+Iol+sVQ1KyoBsHf3veyrBmHCXr3xXRWq6ZXkgVfNLU=
75+
github.com/ava-labs/simplex v0.0.0-20250611154800-78b82e9820e5 h1:rwPm63i5nJ2XIuNjO2H68gDmMKje0VW7orLZMISPrC8=
76+
github.com/ava-labs/simplex v0.0.0-20250611154800-78b82e9820e5/go.mod h1:GVzumIo3zR23/qGRN2AdnVkIPHcKMq/D89EGWZfMGQ0=
7577
github.com/aymerick/raymond v2.0.3-0.20180322193309-b565731e1464+incompatible/go.mod h1:osfaiScAUVup+UC9Nfq76eWqDhXlp+4UYaA8uhTBO6g=
7678
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
7779
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=

simplex/bls.go

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved.
2+
// See the file LICENSE for licensing terms.
3+
4+
package simplex
5+
6+
import (
7+
"errors"
8+
"fmt"
9+
10+
"github.com/ava-labs/simplex"
11+
12+
"github.com/ava-labs/avalanchego/ids"
13+
"github.com/ava-labs/avalanchego/utils/crypto/bls"
14+
)
15+
16+
var (
17+
errSignatureVerificationFailed = errors.New("signature verification failed")
18+
errSignerNotFound = errors.New("signer not found in the membership set")
19+
errInvalidNodeID = errors.New("unable to parse node ID")
20+
errFailedToParseSignature = errors.New("failed to parse signature")
21+
)
22+
23+
var _ simplex.Signer = (*BLSSigner)(nil)
24+
25+
type SignFunc func(msg []byte) (*bls.Signature, error)
26+
27+
// BLSSigner signs messages encoded with the provided ChainID and NetworkID.
28+
// using the SignBLS function.
29+
type BLSSigner struct {
30+
chainID ids.ID
31+
networkID uint32
32+
// signBLS is passed in because we support both software and hardware BLS signing.
33+
signBLS SignFunc
34+
}
35+
36+
type BLSVerifier struct {
37+
nodeID2PK map[ids.NodeID]*bls.PublicKey
38+
networkID uint32
39+
chainID ids.ID
40+
}
41+
42+
func NewBLSAuth(config *Config) (BLSSigner, BLSVerifier) {
43+
verifier := createVerifier(config)
44+
45+
return BLSSigner{
46+
chainID: config.Ctx.ChainID,
47+
networkID: config.Ctx.NetworkID,
48+
signBLS: config.SignBLS,
49+
}, verifier
50+
}
51+
52+
// Sign returns a signature on the given message using BLS signature scheme.
53+
// It encodes the message to sign with the chain ID, and network ID,
54+
func (s *BLSSigner) Sign(message []byte) ([]byte, error) {
55+
message2Sign, err := encodeMessageToSign(message, s.chainID, s.networkID)
56+
if err != nil {
57+
return nil, fmt.Errorf("failed to encode message to sign: %w", err)
58+
}
59+
60+
sig, err := s.signBLS(message2Sign)
61+
if err != nil {
62+
return nil, err
63+
}
64+
65+
sigBytes := bls.SignatureToBytes(sig)
66+
return sigBytes, nil
67+
}
68+
69+
type encodedSimplexSignedPayload struct {
70+
NewtorkID uint32 `serialize:"true"`
71+
ChainID ids.ID `serialize:"true"`
72+
Message []byte `serialize:"true"`
73+
}
74+
75+
func encodeMessageToSign(message []byte, chainID ids.ID, networkID uint32) ([]byte, error) {
76+
encodedSimplexMessage := encodedSimplexSignedPayload{
77+
Message: message,
78+
ChainID: chainID,
79+
NewtorkID: networkID,
80+
}
81+
return Codec.Marshal(CodecVersion, &encodedSimplexMessage)
82+
}
83+
84+
func (v BLSVerifier) Verify(message []byte, signature []byte, signer simplex.NodeID) error {
85+
key, err := ids.ToNodeID(signer)
86+
if err != nil {
87+
return fmt.Errorf("%w: %w", errInvalidNodeID, err)
88+
}
89+
90+
pk, exists := v.nodeID2PK[key]
91+
if !exists {
92+
return fmt.Errorf("%w: signer %x", errSignerNotFound, key)
93+
}
94+
95+
sig, err := bls.SignatureFromBytes(signature)
96+
if err != nil {
97+
return fmt.Errorf("%w: %w", errFailedToParseSignature, err)
98+
}
99+
100+
message2Verify, err := encodeMessageToSign(message, v.chainID, v.networkID)
101+
if err != nil {
102+
return fmt.Errorf("failed to encode message to verify: %w", err)
103+
}
104+
105+
if !bls.Verify(pk, sig, message2Verify) {
106+
return errSignatureVerificationFailed
107+
}
108+
109+
return nil
110+
}
111+
112+
func createVerifier(config *Config) BLSVerifier {
113+
verifier := BLSVerifier{
114+
nodeID2PK: make(map[ids.NodeID]*bls.PublicKey),
115+
networkID: config.Ctx.NetworkID,
116+
chainID: config.Ctx.ChainID,
117+
}
118+
119+
for _, node := range config.Validators {
120+
verifier.nodeID2PK[node.NodeID] = node.PublicKey
121+
}
122+
123+
return verifier
124+
}

simplex/bls_test.go

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved.
2+
// See the file LICENSE for licensing terms.
3+
4+
package simplex
5+
6+
import (
7+
"testing"
8+
9+
"github.com/stretchr/testify/require"
10+
11+
"github.com/ava-labs/avalanchego/ids"
12+
"github.com/ava-labs/avalanchego/utils/crypto/bls"
13+
)
14+
15+
func TestBLSVerifier(t *testing.T) {
16+
config, err := newEngineConfig()
17+
require.NoError(t, err)
18+
signer, verifier := NewBLSAuth(config)
19+
otherNodeID := ids.GenerateTestNodeID()
20+
21+
msg := []byte("Begin at the beginning, and go on till you come to the end: then stop")
22+
tests := []struct {
23+
name string
24+
expectErr error
25+
nodeID []byte
26+
sig []byte
27+
}{
28+
{
29+
name: "valid_signature",
30+
expectErr: nil,
31+
nodeID: config.Ctx.NodeID[:],
32+
sig: func() []byte {
33+
sig, err := signer.Sign(msg)
34+
require.NoError(t, err)
35+
return sig
36+
}(),
37+
},
38+
{
39+
name: "not_in_membership_set",
40+
expectErr: errSignerNotFound,
41+
nodeID: otherNodeID[:],
42+
sig: func() []byte {
43+
sig, err := signer.Sign(msg)
44+
require.NoError(t, err)
45+
return sig
46+
}(),
47+
},
48+
{
49+
name: "invalid_message_encoding",
50+
expectErr: errSignatureVerificationFailed,
51+
nodeID: config.Ctx.NodeID[:],
52+
sig: func() []byte {
53+
sig, err := config.SignBLS(msg)
54+
require.NoError(t, err)
55+
return bls.SignatureToBytes(sig)
56+
}(),
57+
},
58+
{
59+
name: "invalid_nodeID",
60+
expectErr: errInvalidNodeID,
61+
nodeID: []byte{0x01, 0x02, 0x03, 0x04, 0x05}, // Incorrect length NodeID
62+
sig: func() []byte {
63+
sig, err := signer.Sign(msg)
64+
require.NoError(t, err)
65+
return sig
66+
}(),
67+
},
68+
{
69+
name: "nil_signature",
70+
expectErr: errFailedToParseSignature,
71+
nodeID: config.Ctx.NodeID[:],
72+
sig: nil,
73+
},
74+
{
75+
name: "malformed_signature",
76+
expectErr: errFailedToParseSignature,
77+
nodeID: config.Ctx.NodeID[:],
78+
sig: []byte{0x01, 0x02, 0x03}, // Malformed signature
79+
},
80+
}
81+
82+
for _, tt := range tests {
83+
t.Run(tt.name, func(t *testing.T) {
84+
err = verifier.Verify(msg, tt.sig, tt.nodeID)
85+
require.ErrorIs(t, err, tt.expectErr)
86+
})
87+
}
88+
}

simplex/codec.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved.
2+
// See the file LICENSE for licensing terms.
3+
4+
package simplex
5+
6+
import (
7+
"math"
8+
9+
"github.com/ava-labs/avalanchego/codec"
10+
"github.com/ava-labs/avalanchego/codec/linearcodec"
11+
"github.com/ava-labs/avalanchego/vms/platformvm/warp"
12+
)
13+
14+
const CodecVersion = warp.CodecVersion + 1
15+
16+
var Codec codec.Manager
17+
18+
func init() {
19+
lc := linearcodec.NewDefault()
20+
21+
Codec = codec.NewManager(math.MaxInt)
22+
23+
if err := Codec.RegisterCodec(CodecVersion, lc); err != nil {
24+
panic(err)
25+
}
26+
}

simplex/config.go

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved.
2+
// See the file LICENSE for licensing terms.
3+
4+
package simplex
5+
6+
import (
7+
"github.com/ava-labs/avalanchego/ids"
8+
"github.com/ava-labs/avalanchego/snow/validators"
9+
"github.com/ava-labs/avalanchego/utils/logging"
10+
)
11+
12+
// Config wraps all the parameters needed for a simplex engine
13+
type Config struct {
14+
Ctx SimplexChainContext
15+
Log logging.Logger
16+
17+
// Validators is a map of node IDs to their validator information.
18+
// This tells the node about the current membership set, and should be consistent
19+
// across all nodes in the subnet.
20+
Validators map[ids.NodeID]*validators.GetValidatorOutput
21+
22+
// SignBLS is the signing function used for this node to sign messages.
23+
SignBLS SignFunc
24+
}
25+
26+
// Context is information about the current execution.
27+
type SimplexChainContext struct {
28+
// Network is the ID of the network this context exists within.
29+
NodeID ids.NodeID
30+
31+
// ChainID is the ID of the chain this context exists within.
32+
ChainID ids.ID
33+
34+
// NodeID is the ID of this node
35+
NetworkID uint32
36+
}

simplex/test_util.go

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved.
2+
// See the file LICENSE for licensing terms.
3+
4+
package simplex
5+
6+
import (
7+
"github.com/ava-labs/avalanchego/ids"
8+
"github.com/ava-labs/avalanchego/snow/validators"
9+
"github.com/ava-labs/avalanchego/utils/constants"
10+
"github.com/ava-labs/avalanchego/utils/crypto/bls/signer/localsigner"
11+
)
12+
13+
func newTestValidatorInfo(allVds []validators.GetValidatorOutput) map[ids.NodeID]*validators.GetValidatorOutput {
14+
vds := make(map[ids.NodeID]*validators.GetValidatorOutput, len(allVds))
15+
for _, vd := range allVds {
16+
vds[vd.NodeID] = &vd
17+
}
18+
19+
return vds
20+
}
21+
22+
func newEngineConfig() (*Config, error) {
23+
ls, err := localsigner.New()
24+
if err != nil {
25+
return nil, err
26+
}
27+
28+
nodeID := ids.GenerateTestNodeID()
29+
30+
simplexChainContext := SimplexChainContext{
31+
NodeID: nodeID,
32+
ChainID: ids.GenerateTestID(),
33+
NetworkID: constants.UnitTestID,
34+
}
35+
36+
nodeInfo := validators.GetValidatorOutput{
37+
NodeID: nodeID,
38+
PublicKey: ls.PublicKey(),
39+
}
40+
41+
return &Config{
42+
Ctx: simplexChainContext,
43+
Validators: newTestValidatorInfo([]validators.GetValidatorOutput{nodeInfo}),
44+
SignBLS: ls.Sign,
45+
}, nil
46+
}

0 commit comments

Comments
 (0)