Skip to content

Latest commit

 

History

History
93 lines (63 loc) · 3.71 KB

059.md

File metadata and controls

93 lines (63 loc) · 3.71 KB

Slow Misty Iguana

Medium

When verifying the proof in BTCUndelegate(), the BtcConfirmationDepth check was not performed.

Summary

The BtcConfirmationDepth check is not performed during the verification of the proof in BTCUndelegate(), and if the undelegate transaction is rolled back, it will result in the incorrect deduction of vote power on the FP.

Root Cause

https://github.com/sherlock-audit/2024-12-babylon/blob/main/babylon/x/btcstaking/keeper/msg_server.go#L586

func (ms msgServer) BTCUndelegate(goCtx context.Context, req *types.MsgBTCUndelegate) (*types.MsgBTCUndelegateResponse, error) {
	defer telemetry.ModuleMeasureSince(types.ModuleName, time.Now(), types.MetricsKeyBTCUndelegate)

	ctx := sdk.UnwrapSDKContext(goCtx)
	// basic stateless checks
	if err := req.ValidateBasic(); err != nil {
		return nil, status.Errorf(codes.InvalidArgument, "%v", err)
	}

	btcDel, bsParams, err := ms.getBTCDelWithParams(ctx, req.StakingTxHash)

	if err != nil {
		return nil, err
	}

	// ensure the BTC delegation with the given staking tx hash is active
	btcTip := ms.btclcKeeper.GetTipInfo(ctx)

	btcDelStatus := btcDel.GetStatus(
		btcTip.Height,
		bsParams.CovenantQuorum,
	)

	if btcDelStatus == types.BTCDelegationStatus_UNBONDED || btcDelStatus == types.BTCDelegationStatus_EXPIRED {
		return nil, types.ErrInvalidBTCUndelegateReq.Wrap("cannot unbond an unbonded BTC delegation")
	}

	stakeSpendingTx, err := bbn.NewBTCTxFromBytes(req.StakeSpendingTx)

	if err != nil {
		return nil, types.ErrInvalidBTCUndelegateReq.Wrapf("failed to parse staking spending tx: %v", err)
	}

	stakerSpendigTxHeader, err := ms.btclcKeeper.GetHeaderByHash(ctx, req.StakeSpendingTxInclusionProof.Key.Hash)
	if err != nil {
		return nil, types.ErrInvalidBTCUndelegateReq.Wrapf("stake spending tx is not on BTC chain: %v", err)
	}

	btcHeader := stakerSpendigTxHeader.Header.ToBlockHeader()

@>>	proofValid := btcckpttypes.VerifyInclusionProof(
		btcutil.NewTx(stakeSpendingTx),
		&btcHeader.MerkleRoot,
		req.StakeSpendingTxInclusionProof.Proof,
		req.StakeSpendingTxInclusionProof.Key.Index,
	)

	if !proofValid {
		return nil, types.ErrInvalidBTCUndelegateReq.Wrap("stake spending tx is not included in the Bitcoin chain: invalid inclusion proof")
	}

	//skip

	return &types.MsgBTCUndelegateResponse{}, nil
}

We observed that BTCUndelegate() only verifies the proof of the StakeSpendingTx but does not check whether the current BTC height and the height at which the StakeSpendingTx occurred have passed the BtcConfirmationDepth. Since transaction rollbacks can occur on the BTC chain, this check was implemented during the creation of the BTC delegate process for StakingTx in getTimeInfoAndParams(). However, the absence of this check in BTCUndelegate() will lead to issues.

Additionally, BTCUndelegate() lacks access control, meaning anyone can call this interface to report a StakeSpendingTx on the BTC chain, and the gas fee for calling this interface is also refunded.

Internal pre-conditions

External pre-conditions

StakeSpendingTx transaction rollbacks

Attack Path

(1) The attacker monitors the BTC chain and calls the BTCUndelegate() interface to report a StakeSpendingTx as soon as it is included in a block.
(2) If the StakeSpendingTx is rolled back, the FP's vote power on the Babylon chain will be incorrectly deducted. Additionally, rewards and other related calculations for this BTC delegate will also be affected and become erroneous.

Impact

This issue affects the vote power and rewards on the FP (Federation Protocol), as well as the rewards for the BTC delegate.

PoC

Mitigation

The BTCUndelegate() submission is only allowed after the transaction has been confirmed by BtcConfirmationDepth blocks.