Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

FIX: checkpointing/changed PoP by signing BLS public key #97

Merged
merged 3 commits into from
Aug 19, 2022
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
6 changes: 1 addition & 5 deletions cmd/babylond/cmd/testnet.go
Original file line number Diff line number Diff line change
Expand Up @@ -178,13 +178,9 @@ func InitTestnet(
_ = os.RemoveAll(outputDir)
return err
}
accKeyInfo, err := kb.Key(nodeDirName)
if err != nil {
return err
}

// generate validator keys
nodeIDs[i], valKeys[i], err = datagen.InitializeNodeValidatorFiles(nodeConfig, accKeyInfo.GetPubKey())
nodeIDs[i], valKeys[i], err = datagen.InitializeNodeValidatorFiles(nodeConfig)
if err != nil {
_ = os.RemoveAll(outputDir)
return err
Expand Down
4 changes: 2 additions & 2 deletions crypto/bls12381/bls.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ func AggrSigList(sigs []Signature) (Signature, error) {
aggSig := new(BlsMultiSig)
sigBytes := make([][]byte, len(sigs))
for i := 0; i < len(sigs); i++ {
sigBytes[i] = sigs[i].Byte()
sigBytes[i] = sigs[i].Bytes()
}
if !aggSig.AggregateCompressed(sigBytes, false) {
return nil, errors.New("failed to aggregate bls signatures")
Expand All @@ -94,7 +94,7 @@ func AggrPKList(pks []PublicKey) (PublicKey, error) {
aggPk := new(BlsMultiPubKey)
pkBytes := make([][]byte, len(pks))
for i := 0; i < len(pks); i++ {
pkBytes[i] = pks[i].Byte()
pkBytes[i] = pks[i].Bytes()
}
if !aggPk.AggregateCompressed(pkBytes, false) {
return nil, errors.New("failed to aggregate bls public keys")
Expand Down
4 changes: 2 additions & 2 deletions crypto/bls12381/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ func (sig *Signature) Unmarshal(data []byte) error {
return nil
}

func (sig Signature) Byte() []byte {
func (sig Signature) Bytes() []byte {
return sig
}

Expand Down Expand Up @@ -145,7 +145,7 @@ func (pk PublicKey) Equal(k PublicKey) bool {
return string(pk) == string(k)
}

func (pk PublicKey) Byte() []byte {
func (pk PublicKey) Bytes() []byte {
return pk
}

Expand Down
21 changes: 14 additions & 7 deletions privval/types.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
package privval

import (
"errors"

"github.com/babylonchain/babylon/crypto/bls12381"
"github.com/babylonchain/babylon/x/checkpointing/types"
cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types"
tmcrypto "github.com/tendermint/tendermint/crypto"
)

Expand All @@ -16,8 +17,8 @@ type ValidatorKeys struct {
blsPrivkey bls12381.PrivateKey
}

func NewValidatorKeys(valPrivkey tmcrypto.PrivKey, blsPrivKey bls12381.PrivateKey, accPubkey cryptotypes.PubKey) (*ValidatorKeys, error) {
pop, err := BuildPop(valPrivkey, blsPrivKey, accPubkey)
func NewValidatorKeys(valPrivkey tmcrypto.PrivKey, blsPrivKey bls12381.PrivateKey) (*ValidatorKeys, error) {
pop, err := BuildPoP(valPrivkey, blsPrivKey)
if err != nil {
return nil, err
}
Expand All @@ -30,10 +31,16 @@ func NewValidatorKeys(valPrivkey tmcrypto.PrivKey, blsPrivKey bls12381.PrivateKe
}, nil
}

// BuildPop builds a proof-of-possession by encrypt(key = BLS_sk, data = encrypt(key = Ed25519_sk, data = Secp256k1_pk))
// where valPrivKey is Ed25519_sk, accPubkey is Secp256k1_pk, blsPrivkey is BLS_sk
func BuildPop(valPrivKey tmcrypto.PrivKey, blsPrivkey bls12381.PrivateKey, accPubkey cryptotypes.PubKey) (*types.ProofOfPossession, error) {
data, err := valPrivKey.Sign(accPubkey.Bytes())
// BuildPoP builds a proof-of-possession by PoP=sign(key = BLS_sk, data = sign(key = Ed25519_sk, data = BLS_pk))
// where valPrivKey is Ed25519_sk and blsPrivkey is BLS_sk
func BuildPoP(valPrivKey tmcrypto.PrivKey, blsPrivkey bls12381.PrivateKey) (*types.ProofOfPossession, error) {
if valPrivKey == nil {
return nil, errors.New("validator private key is empty")
}
if blsPrivkey == nil {
return nil, errors.New("BLS private key is empty")
}
data, err := valPrivKey.Sign(blsPrivkey.PubKey().Bytes())
if err != nil {
return nil, err
}
Expand Down
2 changes: 1 addition & 1 deletion proto/babylon/checkpointing/bls_key.proto
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ message BlsKey {

// ProofOfPossession defines proof for the ownership of Ed25519 and BLS private keys
message ProofOfPossession {
// bls_sig is calculated by encrypt(key = BLS_sk, data = encrypt(key = Ed25519_sk, data = Secp256k1_pk))
// bls_sig is calculated by sign(key = BLS_sk, data = sign(key = Ed25519_sk, data = BLS_pk))
bytes bls_sig = 1 [
(gogoproto.customtype) = "github.com/babylonchain/babylon/crypto/bls12381.Signature"
];
Expand Down
9 changes: 4 additions & 5 deletions testutil/datagen/init_val.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import (
"fmt"
"github.com/babylonchain/babylon/crypto/bls12381"
"github.com/babylonchain/babylon/privval"
cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types"
"github.com/cosmos/go-bip39"
cfg "github.com/tendermint/tendermint/config"
tmed25519 "github.com/tendermint/tendermint/crypto/ed25519"
Expand All @@ -14,11 +13,11 @@ import (
)

// InitializeNodeValidatorFiles creates private validator and p2p configuration files.
func InitializeNodeValidatorFiles(config *cfg.Config, accPubkey cryptotypes.PubKey) (string, *privval.ValidatorKeys, error) {
return InitializeNodeValidatorFilesFromMnemonic(config, "", accPubkey)
func InitializeNodeValidatorFiles(config *cfg.Config) (string, *privval.ValidatorKeys, error) {
return InitializeNodeValidatorFilesFromMnemonic(config, "")
}

func InitializeNodeValidatorFilesFromMnemonic(config *cfg.Config, mnemonic string, accPubkey cryptotypes.PubKey) (nodeID string, valKeys *privval.ValidatorKeys, err error) {
func InitializeNodeValidatorFilesFromMnemonic(config *cfg.Config, mnemonic string) (nodeID string, valKeys *privval.ValidatorKeys, err error) {
if len(mnemonic) > 0 && !bip39.IsMnemonicValid(mnemonic) {
return "", nil, fmt.Errorf("invalid mnemonic")
}
Expand Down Expand Up @@ -51,7 +50,7 @@ func InitializeNodeValidatorFilesFromMnemonic(config *cfg.Config, mnemonic strin

valPrivkey := filePV.GetValPrivKey()
blsPrivkey := filePV.GetBlsPrivKey()
valKeys, err = privval.NewValidatorKeys(valPrivkey, blsPrivkey, accPubkey)
valKeys, err = privval.NewValidatorKeys(valPrivkey, blsPrivkey)
if err != nil {
return "", nil, err
}
Expand Down
29 changes: 17 additions & 12 deletions x/checkpointing/spec/registration.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,33 +6,38 @@ To participate in the checkpointing, a validator needs to also register its BLS

The original registration is done via a transaction that carries a `MsgCreateValidator` message.
To register a BLS public key, we need a wrapper message called `MsgWrappedCreateValidator` processed by the `Checkpointing` module.
This message wraps the original `MsgCreateValidator` message as well as a new message called `MsgCreateBlsKey` specifically for registering BLS public key.
This message wraps the original `MsgCreateValidator` message as well as a BLS public key and a `Proof-of-Possession` (PoP) for registering BLS public key.
The execution of `MsgWrappedCreateValidator` is as follows.

1. The `Checkpointing` module first processes `MsgCreateBlsKey` to register the validator's BLS key. If success, then
2. delay the processing of `MsgCreateValidator` until the end of this epoch. If success, the registration is succeeded.
3. Otherwise, the corresponding BLS key registered before should be removed to ensure atomicity.
4. The validator should register again.
1. The `Checkpointing` module first processes `MsgWrappedCreateValidator` to register the validator's BLS key. If success, then
2. extract `MsgCreateValidator` and deliver `MsgCreateValidator` to the epoching module's message queue, which will be processed at the end of this epoch. If success, the registration is succeeded.
3. Otherwise, the registration fails and the validator should register again with the same keys.

## Genesis

Genesis validators are registered via the legacy `genutil` module from the Cosmos-SDK, which processes `MsgCreateValidator` messages contained in genesis transactions.
The BLS keys are registered as `GenesisState` in the checkpointing module.
The checkpointing module's `ValidateGenesis` should ensure that each genesis validator has both an Ed25519 key and BLS key which are bonded by PoP.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think the ValidateGenesis method can do this because this seems to run on just the module specific part of the genesis.json part, so it won't be able to see both the genutil and the checkpointing parts, and because there's no execution, it also can't look up validator keys. That's why I suggested there to be a dedicated CLI command to do the validation, by accessing both parts. ValidateGenesis can validate the PoP, though.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the comment, I'll revise the doc in the following PR.


## Proof of Possession

A valid `MsgCreateBlsKey` needs to ensure that the sender of the BLS public key owns:
The purpose of PoP is to prove that one validator owns:
1. the corresponding BLS private key;
2. the corresponding Ed25519 private key associated with the public key in the `MsgCreateValidator` message.

To achieve that, the sender needs to include Proof-of-Possession (PoP) in the `MsgCreateBlsKey` message as follows.
```
MsgCreateBlsKey = [BLS_pk, PoP],
```
where `PoP = [m = Ed25519_pk, sig_BLS = sign(key = BLS_sk, data = m), sig_Ed25519 = sign(key = Ed25519_sk, data = sig_BLS)]`
To achieve that, PoP is calculated as follows.

`PoP = sign(key = BLS_sk, data = sign(key = Ed25519_sk, data = BLS_pk)]`

Since the delegator already relates its account with the validator's Ed25519 key through the signatures in `MsgCreateValidator`, the adversary cannot do registration with the same PoP.

## Verification

To verify PoP, first we need to ensure that the BLS public key has never been registered by a different validator,
and that the current validator hasn't already registered a different BLS public key. Then, verify

```
MsgCreateValidator.Ed25519_pk ?= decrypt(key = BLS_pk, data = decrypt(key = Ed25519_pk, data = PoP.sig_Ed25519))
MsgWrappedCreateValidator.BLS_pk ?= decrypt(key = Ed25519_pk, data = decrypt(key = BLS_pk, data = PoP))
```

If verification passes, the `Checkpointing` module stores the BLS public key and associates it with the validator.
2 changes: 1 addition & 1 deletion x/checkpointing/types/bls_key.pb.go

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