Special Shamrock Giraffe
High
WrappedCreateValidator
does not enforce BLS private key is on curve. Attacker can abuse this and make all further Validator creation request in the epoch fail.
https://github.com/sherlock-audit/2024-12-babylon/blob/main/babylon/x/checkpointing/keeper/msg_server.go#L28 In \babylon\x\checkpointing\keeper\msg_server.go:L28
func (m msgServer) WrappedCreateValidator(goCtx context.Context, msg *types.MsgWrappedCreateValidator) (*types.MsgWrappedCreateValidatorResponse, error) {
...
// store BLS public key
err = m.k.CreateRegistration(ctx, *msg.Key.Pubkey, valAddr)
if err != nil {
return nil, err
}
...
}
func (k Keeper) CreateRegistration(ctx context.Context, blsPubKey bls12381.PublicKey, valAddr sdk.ValAddress) error {
return k.RegistrationState(ctx).CreateRegistration(blsPubKey, valAddr)
}
// CreateRegistration inserts the BLS key into the addr -> key and key -> addr storage
func (rs RegistrationState) CreateRegistration(key bls12381.PublicKey, valAddr sdk.ValAddress) error {
blsPubKey, err := rs.GetBlsPubKey(valAddr)
// we should disallow a validator to register with different BLS public keys
if err == nil && !blsPubKey.Equal(key) {
return types.ErrBlsKeyAlreadyExist.Wrapf("the validator has registered a BLS public key")
}
// we should disallow the same BLS public key is registered by different validators
bkToAddrKey := types.BlsKeyToAddrKey(key)
rawAddr := rs.blsKeysToAddr.Get(bkToAddrKey)
addr := new(sdk.ValAddress)
err = addr.Unmarshal(rawAddr)
if err != nil {
return err
}
if rawAddr != nil && !addr.Equals(valAddr) {
return types.ErrBlsKeyAlreadyExist.Wrapf("same BLS public key is registered by another validator")
}
// save concrete BLS public key object
blsPkKey := types.AddrToBlsKeyKey(valAddr)
rs.addrToBlsKeys.Set(blsPkKey, key)
rs.blsKeysToAddr.Set(bkToAddrKey, valAddr.Bytes())
return nil
}
The BLS pubkey is not verified. This will allow malicious users to push an invalid pubkey, and later when the ABCI BeginBlocker execute the registeration, the check will fail and the whole process is DoSed.
func BeginBlocker(ctx context.Context, k keeper.Keeper) error {
defer telemetry.ModuleMeasureSince(types.ModuleName, time.Now(), telemetry.MetricKeyBeginBlocker)
epoch := k.GetEpoch(ctx)
if epoch.IsFirstBlock(ctx) {
err := k.InitValidatorBLSSet(ctx)
if err != nil {
panic(fmt.Errorf("failed to store validator BLS set: %w", err))
}
}
return nil
}
// InitValidatorBLSSet stores the validator set with BLS keys in the beginning of the current epoch
// This is called upon BeginBlock
func (k Keeper) InitValidatorBLSSet(ctx context.Context) error {
...
for i, val := range valset {
blsPubkey, err := k.GetBlsPubKey(ctx, val.Addr)
if err != nil {
return fmt.Errorf("failed to get BLS public key of address %v: %w", val.Addr, err)
}
...
}
func (k Keeper) GetBlsPubKey(ctx context.Context, address sdk.ValAddress) (bls12381.PublicKey, error) {
return k.RegistrationState(ctx).GetBlsPubKey(address)
}
// GetBlsPubKey retrieves BLS public key by validator's address
func (rs RegistrationState) GetBlsPubKey(addr sdk.ValAddress) (bls12381.PublicKey, error) {
pkKey := types.AddrToBlsKeyKey(addr)
rawBytes := rs.addrToBlsKeys.Get(pkKey)
if rawBytes == nil {
return nil, types.ErrBlsKeyDoesNotExist.Wrapf("BLS public key does not exist with address %s", addr)
}
pk := new(bls12381.PublicKey)
err := pk.Unmarshal(rawBytes)
return *pk, err
}
func (pk *PublicKey) Unmarshal(data []byte) error {
if len(data) != PubKeySize {
return errors.New("Invalid public key length")
}
*pk = data
return nil
}
If attacker's BLS key is not PubKeySize
long, the flow will just return.
N/A
N/A
- Attacker make a WrappedCreateValidator message with incorrect BLS pubkey length
- wait for BeginBlocker.
All validator request in the whole epoch will fail.
No response
Verify BLS pubkey length in WrappedCreateValidator