Skip to content

Commit dde076d

Browse files
committed
add simplex pkg and bls structs
1 parent 697b668 commit dde076d

File tree

3 files changed

+293
-0
lines changed

3 files changed

+293
-0
lines changed

simplex/bls.go

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
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+
"encoding/asn1"
8+
"errors"
9+
"fmt"
10+
11+
"github.com/ava-labs/avalanchego/ids"
12+
"github.com/ava-labs/avalanchego/utils/crypto/bls"
13+
"github.com/ava-labs/simplex"
14+
)
15+
16+
var (
17+
errSignatureVerificationFailed = errors.New("signature verification failed")
18+
errSignerNotFound = errors.New("signer not found in the membership set")
19+
)
20+
21+
var _ simplex.Signer = (*BLSSigner)(nil)
22+
23+
type SignFunc func(msg []byte) (*bls.Signature, error)
24+
25+
// BLSSigner signs messages encoded with the provided ChainID and NetworkID
26+
// using the SignBLS function.
27+
type BLSSigner struct {
28+
chainID ids.ID
29+
subnetID ids.ID
30+
// signBLS is passed in because we support both software and hardware BLS signing.
31+
signBLS SignFunc
32+
nodeID ids.NodeID
33+
}
34+
35+
type BLSVerifier struct {
36+
nodeID2PK map[ids.NodeID]bls.PublicKey
37+
subnetID ids.ID
38+
chainID ids.ID
39+
}
40+
41+
func NewBLSAuth(config *Config) (BLSSigner, BLSVerifier) {
42+
return BLSSigner{
43+
chainID: config.Ctx.ChainID,
44+
subnetID: config.Ctx.SubnetID,
45+
nodeID: config.Ctx.NodeID,
46+
signBLS: config.SignBLS,
47+
}, createVerifier(config)
48+
}
49+
50+
// Sign returns a signature on the given message using BLS signature scheme.
51+
// It encodes the message to sign with the chain ID, and subnet ID,
52+
func (s *BLSSigner) Sign(message []byte) ([]byte, error) {
53+
message2Sign, err := encodeMessageToSign(message, s.chainID, s.subnetID)
54+
if err != nil {
55+
return nil, fmt.Errorf("failed to encode message to sign: %w", err)
56+
}
57+
58+
sig, err := s.signBLS(message2Sign)
59+
if err != nil {
60+
return nil, err
61+
}
62+
63+
sigBytes := bls.SignatureToBytes(sig)
64+
return sigBytes, nil
65+
}
66+
67+
type encodedSimplexMessage struct {
68+
Message []byte
69+
ChainID []byte
70+
SubnetID []byte
71+
}
72+
73+
// encodesMessageToSign returns a byte slice [simplexLabel][chainID][networkID][message length][message].
74+
func encodeMessageToSign(message []byte, chainID ids.ID, subnetID ids.ID) ([]byte, error) {
75+
encodedSimplexMessage := encodedSimplexMessage{
76+
Message: message,
77+
ChainID: chainID[:],
78+
SubnetID: subnetID[:],
79+
}
80+
return asn1.Marshal(encodedSimplexMessage)
81+
}
82+
83+
func (v BLSVerifier) Verify(message []byte, signature []byte, signer simplex.NodeID) error {
84+
if len(signer) != ids.NodeIDLen {
85+
return fmt.Errorf("expected signer to be %d bytes but got %d bytes", ids.NodeIDLen, len(signer))
86+
}
87+
88+
key := ids.NodeID(signer)
89+
pk, exists := v.nodeID2PK[key]
90+
if !exists {
91+
return fmt.Errorf("%w: signer %x", errSignerNotFound, key)
92+
}
93+
94+
sig, err := bls.SignatureFromBytes(signature)
95+
if err != nil {
96+
return fmt.Errorf("failed to parse signature: %w", err)
97+
}
98+
99+
message2Verify, err := encodeMessageToSign(message, v.chainID, v.subnetID)
100+
if err != nil {
101+
return fmt.Errorf("failed to encode message to verify: %w", err)
102+
}
103+
104+
if !bls.Verify(&pk, sig, message2Verify) {
105+
return errSignatureVerificationFailed
106+
}
107+
108+
return nil
109+
}
110+
111+
func createVerifier(config *Config) BLSVerifier {
112+
verifier := BLSVerifier{
113+
nodeID2PK: make(map[ids.NodeID]bls.PublicKey),
114+
subnetID: config.Ctx.SubnetID,
115+
chainID: config.Ctx.ChainID,
116+
}
117+
118+
nodes := config.Validators.GetValidatorIDs(config.Ctx.SubnetID)
119+
for _, node := range nodes {
120+
validator, ok := config.Validators.GetValidator(config.Ctx.SubnetID, node)
121+
if !ok {
122+
continue
123+
}
124+
verifier.nodeID2PK[node] = *validator.PublicKey
125+
}
126+
return verifier
127+
}

simplex/bls_test.go

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
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/ava-labs/avalanchego/ids"
10+
"github.com/ava-labs/avalanchego/snow/validators"
11+
"github.com/ava-labs/avalanchego/utils/crypto/bls"
12+
"github.com/ava-labs/avalanchego/utils/crypto/bls/signer/localsigner"
13+
"github.com/stretchr/testify/require"
14+
)
15+
16+
var _ ValidatorInfo = (*testValidatorInfo)(nil)
17+
18+
// testValidatorInfo is a mock implementation of ValidatorInfo for testing purposes.
19+
// it assumes all validators are in the same subnet and returns all of them for any subnetID.
20+
type testValidatorInfo struct {
21+
validators map[ids.NodeID]validators.Validator
22+
}
23+
24+
func (v *testValidatorInfo) GetValidatorIDs(_ ids.ID) []ids.NodeID {
25+
if v.validators == nil {
26+
return nil
27+
}
28+
29+
ids := make([]ids.NodeID, 0, len(v.validators))
30+
for id := range v.validators {
31+
ids = append(ids, id)
32+
}
33+
return ids
34+
}
35+
36+
func (v *testValidatorInfo) GetValidator(_ ids.ID, nodeID ids.NodeID) (*validators.Validator, bool) {
37+
if v.validators == nil {
38+
return nil, false
39+
}
40+
41+
val, exists := v.validators[nodeID]
42+
if !exists {
43+
return nil, false
44+
}
45+
return &val, true
46+
}
47+
48+
func newTestValidatorInfo(nodeIds []ids.NodeID, pks []*bls.PublicKey) *testValidatorInfo {
49+
if len(nodeIds) != len(pks) {
50+
panic("nodeIds and pks must have the same length")
51+
}
52+
53+
vds := make(map[ids.NodeID]validators.Validator, len(pks))
54+
for i, pk := range pks {
55+
validator := validators.Validator{
56+
PublicKey: pk,
57+
NodeID: nodeIds[i],
58+
}
59+
vds[nodeIds[i]] = validator
60+
}
61+
// all we need is to generate the public keys for the validators
62+
return &testValidatorInfo{
63+
validators: vds,
64+
}
65+
}
66+
67+
func newEngineConfig(ls *localsigner.LocalSigner) *Config {
68+
nodeID := ids.GenerateTestNodeID()
69+
70+
simplexChainContext := SimplexChainContext{
71+
NodeID: nodeID,
72+
ChainID: ids.GenerateTestID(),
73+
SubnetID: ids.GenerateTestID(),
74+
}
75+
76+
return &Config{
77+
Ctx: simplexChainContext,
78+
Validators: newTestValidatorInfo([]ids.NodeID{nodeID}, []*bls.PublicKey{ls.PublicKey()}),
79+
SignBLS: ls.Sign,
80+
}
81+
}
82+
83+
func TestBLSSignVerify(t *testing.T) {
84+
ls, err := localsigner.New()
85+
require.NoError(t, err)
86+
87+
config := newEngineConfig(ls)
88+
89+
signer, verifier := NewBLSAuth(config)
90+
91+
msg := "Begin at the beginning, and go on till you come to the end: then stop"
92+
93+
sig, err := signer.Sign([]byte(msg))
94+
require.NoError(t, err)
95+
96+
err = verifier.Verify([]byte(msg), sig, signer.nodeID[:])
97+
require.NoError(t, err)
98+
}
99+
100+
func TestSignerNotInMemberSet(t *testing.T) {
101+
ls, err := localsigner.New()
102+
require.NoError(t, err)
103+
104+
config := newEngineConfig(ls)
105+
signer, verifier := NewBLSAuth(config)
106+
107+
msg := "Begin at the beginning, and go on till you come to the end: then stop"
108+
109+
sig, err := signer.Sign([]byte(msg))
110+
require.NoError(t, err)
111+
112+
notInMembershipSet := ids.GenerateTestNodeID()
113+
err = verifier.Verify([]byte(msg), sig, notInMembershipSet[:])
114+
require.ErrorIs(t, err, errSignerNotFound)
115+
}
116+
117+
func TestSignerInvalidMessageEncoding(t *testing.T) {
118+
ls, err := localsigner.New()
119+
require.NoError(t, err)
120+
121+
config := newEngineConfig(ls)
122+
123+
// sign a message with invalid encoding
124+
dummyMsg := []byte("dummy message")
125+
sig, err := ls.Sign(dummyMsg)
126+
require.NoError(t, err)
127+
128+
sigBytes := bls.SignatureToBytes(sig)
129+
130+
_, verifier := NewBLSAuth(config)
131+
err = verifier.Verify(dummyMsg, sigBytes, config.Ctx.NodeID[:])
132+
require.ErrorIs(t, err, errSignatureVerificationFailed)
133+
}

simplex/config.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
// Copyright (C) 2019-2025, 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+
type ValidatorInfo interface {
13+
GetValidatorIDs(subnetID ids.ID) []ids.NodeID
14+
GetValidator(subnetID ids.ID, nodeID ids.NodeID) (*validators.Validator, bool)
15+
}
16+
17+
// Config wraps all the parameters needed for a simplex engine
18+
type Config struct {
19+
Ctx SimplexChainContext
20+
Log logging.Logger
21+
Validators ValidatorInfo
22+
SignBLS SignFunc
23+
}
24+
25+
// Context is information about the current execution.
26+
// [SubnitID] is the ID of the subnet this context exists within.
27+
// [ChainID] is the ID of the chain this context exists within.
28+
// [NodeID] is the ID of this node
29+
type SimplexChainContext struct {
30+
NodeID ids.NodeID
31+
ChainID ids.ID
32+
SubnetID ids.ID
33+
}

0 commit comments

Comments
 (0)