-
Notifications
You must be signed in to change notification settings - Fork 5
Description
Description
As babylon genesis support multisig btc delegation, Vigilante also needs corresponding changes in btcstaking-tracker:
1. stakingeventwatcher
tryParseStakerSignatureFromSpentTx should properly parse staker's signatures even in case of M-of-N multisig signatures.
vigilante/btcstaking-tracker/stakingeventwatcher/stakingeventwatcher.go
Lines 352 to 383 in c3b470a
| // tryParseStakerSignatureFromSpentTx tries to parse staker signature from unbonding tx. | |
| // If provided tx is not unbonding tx it returns error. | |
| func tryParseStakerSignatureFromSpentTx(tx *wire.MsgTx, td *TrackedDelegation) (*schnorr.Signature, error) { | |
| if len(tx.TxOut) != 1 { | |
| return nil, fmt.Errorf("unbonding tx must have exactly one output. Priovided tx has %d outputs", len(tx.TxOut)) | |
| } | |
| if tx.TxOut[0].Value != td.UnbondingOutput.Value || !bytes.Equal(tx.TxOut[0].PkScript, td.UnbondingOutput.PkScript) { | |
| return nil, fmt.Errorf("unbonding tx must have output which matches unbonding output of retrieved from Babylon") | |
| } | |
| stakingTxInputIdx, err := getStakingTxInputIdx(tx, td) | |
| if err != nil { | |
| return nil, fmt.Errorf("unbonding tx does not spend staking output: %w", err) | |
| } | |
| stakingTxInput := tx.TxIn[stakingTxInputIdx] | |
| witnessLen := len(stakingTxInput.Witness) | |
| // minimal witness size for staking tx input is 4: | |
| // covenant_signature, staker_signature, script, control_block | |
| // If that is not the case, something weird is going on and we should investigate. | |
| if witnessLen < 4 { | |
| panic(fmt.Errorf("staking tx input witness has less than 4 elements for unbonding tx %s", tx.TxHash())) | |
| } | |
| // staker signature is 3rd element from the end | |
| stakerSignature := stakingTxInput.Witness[witnessLen-3] | |
| return schnorr.ParseSignature(stakerSignature) | |
| } | |
func tryParseStakerSignatureFromSpentTx(tx *wire.MsgTx, td *TrackedDelegation) (*schnorr.Signature, error) {
// ...
stakingTxInput := tx.TxIn[stakingTxInputIdx]
witnessLen := len(stakingTxInput.Witness)
// minimal witness size for staking tx input is at least 4:
// covenant_signatures, staker_signatures, script, control_block
// If that is not the case, something weird is going on and we should investigate.
if witnessLen < 4 {
panic(fmt.Errorf("staking tx input witness has less than 4 elements for unbonding tx %s", tx.TxHash()))
}
// staker signatures started from 3rd element from the end
+ // TODO: there are more than one stakerSignature if it's multisig btc delegation, so we need to handle this case properly
stakerSignature := stakingTxInput.Witness[witnessLen-3]
return schnorr.ParseSignature(stakerSignature)
}and also we need to add IsStakerMultisig field to TrackedDelegation to parse depending on M-of-N multisig.
| type TrackedDelegation struct { | |
| StakingTx *wire.MsgTx | |
| StakingOutputIdx uint32 | |
| UnbondingOutput *wire.TxOut | |
| DelegationStartHeight uint32 | |
| InProgress bool | |
| } |
But there's one problem when parsing stakerSignature which is it's hard to calculate exact number of staker signatures since there are covenant signatures of greater or equal to covenant quorum and staker signatures of greater or equal to staker quorum.
e.g., let's say covenant committee is using 3-of-5 multisig and staker is using 2-of-3 multisig. there are multiple scenarios of the number of signatures (covenant_sigs, staker_sigs): (3, 2), (3, 3), (4, 2), (4, 3), (5, 2), (5, 3).
However, since M-of-N multisig with OP_CHECKSIGADD requires exact amount of signatures equal to the number of total size of multisig party (N), even though it's nil. So we can parse signature of N. (Can u confirm this approach is correct? @KonradStaniec )
2. btcslasher
BuildUnbondingSlashingTxWithWitness and BuildSlashingTxWithWitness should also handle multisig btc delegation.
vigilante/btcstaking-tracker/btcslasher/slasher_utils.go
Lines 339 to 386 in c3b470a
| // BuildUnbondingSlashingTxWithWitness returns the unbonding slashing tx. | |
| func BuildUnbondingSlashingTxWithWitness( | |
| d *bstypes.BTCDelegationResponse, | |
| bsParams *bstypes.Params, | |
| btcNet *chaincfg.Params, | |
| fpSK *btcec.PrivateKey, | |
| ) (*wire.MsgTx, error) { | |
| if d.UnbondingTime > uint32(^uint16(0)) { | |
| panic(fmt.Errorf("unbondingTime (%d) exceeds maximum for uint16", d.UnbondingTime)) | |
| } | |
| config := SlashingConfig{ | |
| TxHex: d.UndelegationResponse.UnbondingTxHex, | |
| SlashingTxHex: d.UndelegationResponse.SlashingTxHex, | |
| SlashingSigHex: d.UndelegationResponse.DelegatorSlashingSigHex, | |
| CovenantSigs: d.UndelegationResponse.CovenantSlashingSigs, | |
| OutputIdx: 0, | |
| InfoBuilder: func() (StakingInfoProvider, error) { | |
| unbondingMsgTx, _, err := bbn.NewBTCTxFromHex(d.UndelegationResponse.UnbondingTxHex) | |
| if err != nil { | |
| return nil, err | |
| } | |
| fpBtcPkList, err := bbn.NewBTCPKsFromBIP340PKs(d.FpBtcPkList) | |
| if err != nil { | |
| return nil, err | |
| } | |
| covenantBtcPkList, err := bbn.NewBTCPKsFromBIP340PKs(bsParams.CovenantPks) | |
| if err != nil { | |
| return nil, err | |
| } | |
| // #nosec G115 -- performed the conversion check above | |
| return btcstaking.BuildUnbondingInfo( | |
| d.BtcPk.MustToBTCPK(), | |
| fpBtcPkList, | |
| covenantBtcPkList, | |
| bsParams.CovenantQuorum, | |
| uint16(d.UnbondingTime), | |
| btcutil.Amount(unbondingMsgTx.TxOut[0].Value), | |
| btcNet, | |
| ) | |
| }, | |
| } | |
| return buildSlashingTxWithWitness(d, bsParams, fpSK, config) | |
| } |
vigilante/btcstaking-tracker/btcslasher/slasher_utils.go
Lines 400 to 447 in c3b470a
| // BuildSlashingTxWithWitness constructs a Bitcoin slashing transaction with the required witness data | |
| // using the provided finality provider's private key. It handles the conversion and validation of | |
| // various parameters needed for slashing a Bitcoin delegation, including the staking transaction, | |
| // finality provider public keys, and covenant public keys. | |
| // Note: this function is UNSAFE for concurrent accesses as slashTx.BuildSlashingTxWithWitness is not safe for | |
| // concurrent access inside it's calling asig.NewDecyptionKeyFromBTCSK | |
| func BuildSlashingTxWithWitness( | |
| d *bstypes.BTCDelegationResponse, | |
| bsParams *bstypes.Params, | |
| btcNet *chaincfg.Params, | |
| fpSK *btcec.PrivateKey, | |
| ) (*wire.MsgTx, error) { | |
| if d.TotalSat > math.MaxInt64 { | |
| panic(fmt.Errorf("TotalSat %d exceeds int64 range", d.TotalSat)) | |
| } | |
| config := SlashingConfig{ | |
| TxHex: d.StakingTxHex, | |
| SlashingTxHex: d.SlashingTxHex, | |
| SlashingSigHex: d.DelegatorSlashSigHex, | |
| CovenantSigs: d.CovenantSigs, | |
| OutputIdx: d.StakingOutputIdx, | |
| InfoBuilder: func() (StakingInfoProvider, error) { | |
| fpBtcPkList, err := bbn.NewBTCPKsFromBIP340PKs(d.FpBtcPkList) | |
| if err != nil { | |
| return nil, err | |
| } | |
| covenantBtcPkList, err := bbn.NewBTCPKsFromBIP340PKs(bsParams.CovenantPks) | |
| if err != nil { | |
| return nil, err | |
| } | |
| // #nosec G115 -- performed the conversion check above | |
| return btcstaking.BuildStakingInfo( | |
| d.BtcPk.MustToBTCPK(), | |
| fpBtcPkList, | |
| covenantBtcPkList, | |
| bsParams.CovenantQuorum, | |
| uint16(d.EndHeight-d.StartHeight), | |
| btcutil.Amount(d.TotalSat), | |
| btcNet, | |
| ) | |
| }, | |
| } | |
| return buildSlashingTxWithWitness(d, bsParams, fpSK, config) | |
| } |
specifically, we need to implement buildSlashingTxWithWitness to handle multisig btc delegation. i.e., using BuildMultisigSlashingTxWithWitness(babylonlabs-io/babylon#1861) of babylon/x/btcstaking/types/btc_slashing_tx.go. this function would require list of delSlashingSig instead of a single sig.
To implement this, we will introduce a new field in SlashingConfig, that contains delegatorSlashingSigs and delegatorUnbondingSlashingSigs.
vigilante/btcstaking-tracker/btcslasher/slasher_utils.go
Lines 38 to 46 in c3b470a
| // SlashingConfig holds configuration for building slashing transactions | |
| type SlashingConfig struct { | |
| TxHex string | |
| SlashingTxHex string | |
| SlashingSigHex string | |
| CovenantSigs []*bstypes.CovenantAdaptorSignatures | |
| OutputIdx uint32 | |
| InfoBuilder func() (StakingInfoProvider, error) | |
| } |