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

zoneconcierge: verifying BLS multisig in ProofEpochSealed #241

Merged
merged 7 commits into from
Dec 9, 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
42 changes: 42 additions & 0 deletions testutil/datagen/epoching.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package datagen

import (
"math/rand"

epochingtypes "github.com/babylonchain/babylon/x/epoching/types"
)

// firstBlockHeight returns the height of the first block of a given epoch and epoch interval
// NOTE: this is only a function for testing and assumes static epoch interval
func firstBlockHeight(epochNumber uint64, epochInterval uint64) uint64 {
if epochNumber == 0 {
return 0
} else {
return (epochNumber-1)*epochInterval + 1
}
}

func GenRandomEpochNum() uint64 {
epochNum := rand.Int63n(100)
return uint64(epochNum)
}

func GenRandomEpochInterval() uint64 {
epochInterval := rand.Int63n(10) + 2 // interval should be at least 2
return uint64(epochInterval)
}

func GenRandomEpoch() *epochingtypes.Epoch {
epochNum := GenRandomEpochNum()
epochInterval := GenRandomEpochInterval()
firstBlockHeight := firstBlockHeight(epochNum, epochInterval)
lastBlockHeader := GenRandomTMHeader("test-chain", firstBlockHeight+epochInterval-1)
epoch := epochingtypes.NewEpoch(
epochNum,
epochInterval,
firstBlockHeight,
lastBlockHeader,
)
epoch.SealerHeader = GenRandomTMHeader("test-chain", firstBlockHeight+epochInterval+1) // 2nd block in the next epoch
return &epoch
}
45 changes: 17 additions & 28 deletions testutil/datagen/raw_checkpoint.go
Original file line number Diff line number Diff line change
@@ -1,16 +1,31 @@
package datagen

import (
"github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1"
sdk "github.com/cosmos/cosmos-sdk/types"
"math/rand"

sdk "github.com/cosmos/cosmos-sdk/types"

"github.com/babylonchain/babylon/btctxformatter"
txformat "github.com/babylonchain/babylon/btctxformatter"
"github.com/babylonchain/babylon/crypto/bls12381"
"github.com/babylonchain/babylon/x/checkpointing/types"
"github.com/boljen/go-bitmap"
)

// GenRandomBitmap generates a random bitmap for the validator set
// It returns a random bitmap and the number of validators in the subset
func GenRandomBitmap() (bitmap.Bitmap, int) {
bmBytes := GenRandomByteArray(txformat.BitMapLength)
bm := bitmap.Bitmap(bmBytes)
numSubset := 0
for i := 0; i < bm.Len(); i++ {
if bitmap.Get(bm, i) {
numSubset++
}
}
return bm, numSubset
}

func GetRandomRawBtcCheckpoint() *btctxformatter.RawBtcCheckpoint {
rawCkpt := GenRandomRawCheckpoint()
return &btctxformatter.RawBtcCheckpoint{
Expand Down Expand Up @@ -67,27 +82,6 @@ func GenRandomSequenceRawCheckpointsWithMeta() []*types.RawCheckpointWithMeta {
return checkpoints
}

func GenerateValidatorSetWithBLSPrivKeys(n int) (*types.ValidatorWithBlsKeySet, []bls12381.PrivateKey) {
valSet := &types.ValidatorWithBlsKeySet{
ValSet: make([]*types.ValidatorWithBlsKey, n),
}
blsPrivKeys := make([]bls12381.PrivateKey, n)

for i := 0; i < n; i++ {
addr := sdk.ValAddress(secp256k1.GenPrivKey().PubKey().Address())
blsPrivkey := bls12381.GenPrivKey()
val := &types.ValidatorWithBlsKey{
ValidatorAddress: addr.String(),
BlsPubKey: blsPrivkey.PubKey(),
VotingPower: 1000,
}
valSet.ValSet[i] = val
blsPrivKeys[i] = blsPrivkey
}

return valSet, blsPrivKeys
}

func GenerateBLSSigs(keys []bls12381.PrivateKey, msg []byte) []bls12381.Signature {
var sigs []bls12381.Signature
for _, privkey := range keys {
Expand Down Expand Up @@ -122,11 +116,6 @@ func GenerateLegitimateRawCheckpoint(privKeys []bls12381.PrivateKey) *types.RawC
return btcCheckpoint
}

func GenRandomEpochNum() uint64 {
epochNum := rand.Int63n(100)
return uint64(epochNum)
}

func GenRandomLastCommitHash() types.LastCommitHash {
return GenRandomByteArray(types.HashSize)
}
Expand Down
8 changes: 8 additions & 0 deletions testutil/datagen/tendermint.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,14 @@ import (
tmproto "github.com/tendermint/tendermint/proto/tendermint/types"
)

func GenRandomTMHeader(chainID string, height uint64) *tmproto.Header {
return &tmproto.Header{
ChainID: chainID,
Height: int64(height),
LastCommitHash: GenRandomByteArray(32),
}
}

func GenRandomIBCTMHeader(chainID string, height uint64) *ibctmtypes.Header {
return &ibctmtypes.Header{
SignedHeader: &tmproto.SignedHeader{
Expand Down
23 changes: 23 additions & 0 deletions testutil/datagen/val_set.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@ package datagen

import (
"github.com/babylonchain/babylon/crypto/bls12381"
checkpointingtypes "github.com/babylonchain/babylon/x/checkpointing/types"
epochingtypes "github.com/babylonchain/babylon/x/epoching/types"
"github.com/cosmos/cosmos-sdk/crypto/keys/ed25519"
"github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1"
sdk "github.com/cosmos/cosmos-sdk/types"
)

Expand Down Expand Up @@ -35,3 +37,24 @@ func GenRandomPubkeysAndSigs(n int, msg []byte) ([]bls12381.PublicKey, []bls1238

return blsPubkeys, blsSigs
}

func GenerateValidatorSetWithBLSPrivKeys(n int) (*checkpointingtypes.ValidatorWithBlsKeySet, []bls12381.PrivateKey) {
valSet := &checkpointingtypes.ValidatorWithBlsKeySet{
ValSet: make([]*checkpointingtypes.ValidatorWithBlsKey, n),
}
blsPrivKeys := make([]bls12381.PrivateKey, n)

for i := 0; i < n; i++ {
addr := sdk.ValAddress(secp256k1.GenPrivKey().PubKey().Address())
blsPrivkey := bls12381.GenPrivKey()
val := &checkpointingtypes.ValidatorWithBlsKey{
ValidatorAddress: addr.String(),
BlsPubKey: blsPrivkey.PubKey(),
VotingPower: 1000,
}
valSet.ValSet[i] = val
blsPrivKeys[i] = blsPrivkey
}

return valSet, blsPrivKeys
}
6 changes: 6 additions & 0 deletions x/checkpointing/types/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,12 @@ func (m RawCheckpoint) Hash() RawCkptHash {
return hash(fields)
}

// SignedMsg is the message corresponding to the BLS sig in this raw checkpoint
// Its value should be (epoch_number || last_commit_hash)
func (m RawCheckpoint) SignedMsg() []byte {
Copy link
Contributor

Choose a reason for hiding this comment

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

Nice! 👏

return append(sdk.Uint64ToBigEndian(m.EpochNum), *m.LastCommitHash...)
}

func hash(fields [][]byte) []byte {
var bz []byte
for _, b := range fields {
Expand Down
12 changes: 10 additions & 2 deletions x/checkpointing/types/val_bls_set.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package types

import (
fmt "fmt"

"github.com/babylonchain/babylon/crypto/bls12381"
"github.com/boljen/go-bitmap"
"github.com/cosmos/cosmos-sdk/codec"
Expand All @@ -18,18 +20,24 @@ func BytesToValidatorBlsKeySet(cdc codec.BinaryCodec, bz []byte) (*ValidatorWith

// FindSubsetWithPowerSum returns a subset and the sum of the voting Power
// based on the given bitmap
func (ks *ValidatorWithBlsKeySet) FindSubsetWithPowerSum(bm bitmap.Bitmap) (*ValidatorWithBlsKeySet, uint64) {
func (ks *ValidatorWithBlsKeySet) FindSubsetWithPowerSum(bm bitmap.Bitmap) (*ValidatorWithBlsKeySet, uint64, error) {
var sum uint64
valSet := &ValidatorWithBlsKeySet{
ValSet: make([]*ValidatorWithBlsKey, 0),
}

// ensure bitmap is big enough to contain ks
if bm.Len() < len(ks.ValSet) {
return valSet, sum, fmt.Errorf("bitmap (with %d bits) is not large enough to contain the validator set with size %d", bm.Len(), len(ks.ValSet))
}

for i := 0; i < len(ks.ValSet); i++ {
if bm.Get(i) {
valSet.ValSet = append(valSet.ValSet, ks.ValSet[i])
sum += ks.ValSet[i].VotingPower
}
}
return valSet, sum
return valSet, sum, nil
}

func (ks *ValidatorWithBlsKeySet) GetBLSKeySet() []bls12381.PublicKey {
Expand Down
5 changes: 3 additions & 2 deletions x/epoching/keeper/epochs.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ func (k Keeper) InitEpoch(ctx sdk.Context) {
panic("InitEpoch can be invoked only at genesis")
}
epochInterval := k.GetParams(ctx).EpochInterval
epoch := types.NewEpoch(0, epochInterval, &header)
epoch := types.NewEpoch(0, epochInterval, 0, &header)
k.setEpochInfo(ctx, 0, &epoch)

k.setEpochNumber(ctx, 0)
Expand Down Expand Up @@ -111,13 +111,14 @@ func (k Keeper) RecordSealerHeaderForPrevEpoch(ctx sdk.Context) *types.Epoch {
}

// IncEpoch adds epoch number by 1
// CONTRACT: can only be invoked at the first block of an epoch
func (k Keeper) IncEpoch(ctx sdk.Context) types.Epoch {
epochNumber := k.GetEpoch(ctx).EpochNumber
incrementedEpochNumber := epochNumber + 1
k.setEpochNumber(ctx, incrementedEpochNumber)

epochInterval := k.GetParams(ctx).EpochInterval
newEpoch := types.NewEpoch(incrementedEpochNumber, epochInterval, nil)
newEpoch := types.NewEpoch(incrementedEpochNumber, epochInterval, uint64(ctx.BlockHeight()), nil)
k.setEpochInfo(ctx, incrementedEpochNumber, &newEpoch)

return newEpoch
Expand Down
7 changes: 6 additions & 1 deletion x/epoching/keeper/grpc_query_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,14 +72,19 @@ func FuzzCurrentEpoch(f *testing.F) {
helper := testepoching.NewHelper(t)
ctx, keeper, queryClient := helper.Ctx, helper.EpochingKeeper, helper.QueryClient
wctx := sdk.WrapSDKContext(ctx)

epochInterval := keeper.GetParams(ctx).EpochInterval
for i := uint64(0); i < increment; i++ {
// this ensures that IncEpoch is invoked only at the first header of each epoch
ctx = ctx.WithBlockHeader(*datagen.GenRandomTMHeader("chain-test", i*epochInterval+1))
wctx = sdk.WrapSDKContext(ctx)
keeper.IncEpoch(ctx)
}
req := types.QueryCurrentEpochRequest{}
resp, err := queryClient.CurrentEpoch(wctx, &req)
require.NoError(t, err)
require.Equal(t, increment, resp.CurrentEpoch)
require.Equal(t, increment*keeper.GetParams(ctx).EpochInterval, resp.EpochBoundary)
require.Equal(t, increment*epochInterval, resp.EpochBoundary)
})
}

Expand Down
33 changes: 17 additions & 16 deletions x/epoching/types/epoching.go
Original file line number Diff line number Diff line change
@@ -1,36 +1,29 @@
package types

import (
"time"

codectypes "github.com/cosmos/cosmos-sdk/codec/types"
cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types"
"time"

sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/tendermint/tendermint/crypto/tmhash"
tmproto "github.com/tendermint/tendermint/proto/tendermint/types"
)

func NewEpoch(epochNumber uint64, epochInterval uint64, lastBlockHeader *tmproto.Header) Epoch {
// NewEpoch constructs a new Epoch object
// The relationship between block and epoch is as follows, assuming epoch interval of 5:
// 0 | 1 2 3 4 5 | 6 7 8 9 10 |
// 0 | 1 | 2 |
func NewEpoch(epochNumber uint64, epochInterval uint64, firstBlockHeight uint64, lastBlockHeader *tmproto.Header) Epoch {
return Epoch{
EpochNumber: epochNumber,
CurrentEpochInterval: epochInterval,
FirstBlockHeight: firstBlockHeight(epochNumber, epochInterval),
FirstBlockHeight: firstBlockHeight,
LastBlockHeader: lastBlockHeader,
}
}

// firstBlockHeight returns the height of the first block of a given epoch and epoch interval
// TODO (non-urgent): add support to variable epoch interval
func firstBlockHeight(epochNumber uint64, epochInterval uint64) uint64 {
// example: in epoch 2, epoch interval is 5 blocks, FirstBlockHeight will be (2-1)*5+1 = 6
// 0 | 1 2 3 4 5 | 6 7 8 9 10 |
// 0 | 1 | 2 |
if epochNumber == 0 {
return 0
} else {
return (epochNumber-1)*epochInterval + 1
// NOTE: SealerHeader will be set in the next epoch
}
}

Expand Down Expand Up @@ -77,6 +70,14 @@ func (e Epoch) IsFirstBlockOfNextEpoch(ctx sdk.Context) bool {
}
}

// ValidateBasic does sanity checks on Epoch
func (e Epoch) ValidateBasic() error {
if e.CurrentEpochInterval < 2 {
return ErrInvalidEpoch.Wrapf("CurrentEpochInterval (%d) < 2", e.CurrentEpochInterval)
}
return nil
}

// NewQueuedMessage creates a new QueuedMessage from a wrapped msg
// i.e., wrapped -> unwrapped -> QueuedMessage
func NewQueuedMessage(blockHeight uint64, blockTime time.Time, txid []byte, msg sdk.Msg) (QueuedMessage, error) {
Expand Down
1 change: 1 addition & 0 deletions x/epoching/types/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,5 @@ var (
ErrUnmarshal = sdkerrors.Register(ModuleName, 9, "unmarshal error.")
ErrNoWrappedMsg = sdkerrors.Register(ModuleName, 10, "the wrapped msg contains no msg inside.")
ErrZeroEpochMsg = sdkerrors.Register(ModuleName, 11, "the 0-th epoch does not handle messages")
ErrInvalidEpoch = sdkerrors.Register(ModuleName, 12, "the epoch is invalid")
)
19 changes: 13 additions & 6 deletions x/epoching/types/validator.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package types
import (
"bytes"
"encoding/json"
fmt "fmt"
"sort"

"github.com/boljen/go-bitmap"
Expand Down Expand Up @@ -45,13 +46,19 @@ func (vs ValidatorSet) FindValidatorWithIndex(valAddr sdk.ValAddress) (*Validato
return &vs[index], index, nil
}

func (vs ValidatorSet) FindSubset(bitmap bitmap.Bitmap) (ValidatorSet, error) {
func (vs ValidatorSet) FindSubset(bm bitmap.Bitmap) (ValidatorSet, error) {
valSet := make([]Validator, 0)
for i := 0; i < bitmap.Len(); i++ {
if bitmap.Get(i) {
if i >= len(vs) {
return nil, errors.New("invalid validator index")
}

// ensure bitmap is big enough to contain vs
if bm.Len() < len(vs) {
return valSet, fmt.Errorf("bitmap (with %d bits) is not large enough to contain the validator set with size %d", bm.Len(), len(vs))
}

// NOTE: we cannot use bm.Len() to iterate over the bitmap
// Our bm has 13 bytes = 104 bits, while the validator set has 100 validators
// If iterating over the 104 bits, then the last 4 bits will trigger the our of range error in ks
for i := 0; i < len(vs); i++ {
if bm.Get(i) {
valSet = append(valSet, vs[i])
}
}
Expand Down
Loading