Slow Misty Iguana
Medium
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.
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.
StakeSpendingTx
transaction rollbacks
(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.
This issue affects the vote power and rewards on the FP (Federation Protocol), as well as the rewards for the BTC delegate.
The BTCUndelegate()
submission is only allowed after the transaction has been confirmed by BtcConfirmationDepth
blocks.