From 130fd6856ab04bad3c24f045d02b81e8876c736a Mon Sep 17 00:00:00 2001 From: KonradStaniec Date: Thu, 18 Jul 2024 15:23:31 +0200 Subject: [PATCH] Use bip322 to build pop through bitcoind (#186) * Use bip322 to build pop through bitcoind --- babylonclient/pop.go | 38 +++++++++++++++++++++++++++++++ staker/stakerapp.go | 46 ++++++++++++++------------------------ walletcontroller/client.go | 16 +++++++++++-- 3 files changed, 69 insertions(+), 31 deletions(-) diff --git a/babylonclient/pop.go b/babylonclient/pop.go index e527411..4e5c651 100644 --- a/babylonclient/pop.go +++ b/babylonclient/pop.go @@ -3,10 +3,13 @@ package babylonclient import ( "fmt" + "github.com/babylonchain/babylon/crypto/bip322" bbn "github.com/babylonchain/babylon/types" btcstypes "github.com/babylonchain/babylon/x/btcstaking/types" "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg" + "github.com/btcsuite/btcd/wire" sdk "github.com/cosmos/cosmos-sdk/types" ) @@ -23,6 +26,8 @@ type BabylonPop struct { BtcSig []byte } +// NewBabylonPop Generic constructor for BabylonPop that do as little validation +// as possible. It assumes passed btcSigOverBbnAddr is matching the popType `t` func NewBabylonPop(t BabylonBtcPopType, btcSigOverBbnAddr []byte) (*BabylonPop, error) { if len(btcSigOverBbnAddr) == 0 { return nil, fmt.Errorf("cannot create BabylonPop with empty signatures") @@ -34,6 +39,39 @@ func NewBabylonPop(t BabylonBtcPopType, btcSigOverBbnAddr []byte) (*BabylonPop, }, nil } +// NewBabylonBip322Pop build proper BabylonPop in BIP322 style, it verifies the +// the bip322 signature validity +func NewBabylonBip322Pop( + msg []byte, + w wire.TxWitness, + a btcutil.Address) (*BabylonPop, error) { + // TODO: bip322.Verify does not use it last parameter and this parameter should + // be removed from the function signature upstream. + // after that, we can remove the nil parameter here + if err := bip322.Verify(msg, w, a, nil); err != nil { + return nil, fmt.Errorf("invalid bip322 pop parameters: %w", err) + } + + serializedWitness, err := bip322.SerializeWitness(w) + + if err != nil { + return nil, fmt.Errorf("failed to serialize bip322 witness: %w", err) + } + + bip322Sig := btcstypes.BIP322Sig{ + Sig: serializedWitness, + Address: a.EncodeAddress(), + } + + m, err := bip322Sig.Marshal() + + if err != nil { + return nil, fmt.Errorf("failed to serialize btcstypes.BIP322Sig proto: %w", err) + } + + return NewBabylonPop(Bip322Type, m) +} + func NewBTCSigType(t BabylonBtcPopType) (btcstypes.BTCSigType, error) { switch t { case SchnorrType: diff --git a/staker/stakerapp.go b/staker/stakerapp.go index 319e74c..fc35cbf 100644 --- a/staker/stakerapp.go +++ b/staker/stakerapp.go @@ -8,9 +8,6 @@ import ( "sync/atomic" "time" - pv "github.com/cosmos/relayer/v2/relayer/provider" - "go.uber.org/zap" - "github.com/avast/retry-go/v4" staking "github.com/babylonchain/babylon/btcstaking" cl "github.com/babylonchain/btc-staker/babylonclient" @@ -21,6 +18,8 @@ import ( "github.com/babylonchain/btc-staker/types" "github.com/babylonchain/btc-staker/utils" "github.com/babylonchain/btc-staker/walletcontroller" + pv "github.com/cosmos/relayer/v2/relayer/provider" + "go.uber.org/zap" "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcec/v2/schnorr" @@ -1274,28 +1273,6 @@ func (app *StakerApp) BabylonController() cl.BabylonClient { return app.babylonClient } -// Generate proof of possessions for staker address. -// Requires btc wallet to be unlocked! -func (app *StakerApp) generatePop(stakerPrivKey *btcec.PrivateKey) (*cl.BabylonPop, error) { - // build proof of possession, no point moving forward if staker does not have all - // the necessary keys - babylonAddrHash := tmhash.Sum(app.babylonClient.GetKeyAddress().Bytes()) - btcSig, err := schnorr.Sign(stakerPrivKey, babylonAddrHash) - if err != nil { - return nil, err - } - - pop, err := cl.NewBabylonPop( - cl.SchnorrType, - btcSig.Serialize(), - ) - if err != nil { - return nil, fmt.Errorf("failed to generate pop: %w", err) - } - - return pop, nil -} - func GetMinStakingTime(p *cl.StakingParams) uint32 { // Actual minimum staking time in babylon is k+w, but setting it to that would // result in delegation which have voting power for 0 btc blocks. @@ -1450,21 +1427,32 @@ func (app *StakerApp) StakeFunds( // build proof of possesion, no point moving forward if staker do not have all // the necessary keys - stakerPrivKey, err := app.wc.DumpPrivateKey(stakerAddress) + stakerPubKey, err := app.wc.AddressPublicKey(stakerAddress) if err != nil { return nil, err } - // We build pop ourselves so no need to verify it - pop, err := app.generatePop(stakerPrivKey) + babylonAddrHash := tmhash.Sum(app.babylonClient.GetKeyAddress().Bytes()) + + sig, err := app.wc.SignBip322NativeSegwit(babylonAddrHash, stakerAddress) + + if err != nil { + return nil, err + } + + pop, err := cl.NewBabylonBip322Pop( + babylonAddrHash, + sig, + stakerAddress, + ) if err != nil { return nil, err } stakingInfo, err := staking.BuildStakingInfo( - stakerPrivKey.PubKey(), + stakerPubKey, fpPks, params.CovenantPks, params.CovenantQuruomThreshold, diff --git a/walletcontroller/client.go b/walletcontroller/client.go index 4f6f8ad..1fa0229 100644 --- a/walletcontroller/client.go +++ b/walletcontroller/client.go @@ -99,13 +99,25 @@ func (w *RpcWalletController) UnlockWallet(timoutSec int64) error { } func (w *RpcWalletController) AddressPublicKey(address btcutil.Address) (*btcec.PublicKey, error) { - privKey, err := w.DumpPrivKey(address) + encoded := address.EncodeAddress() + + info, err := w.GetAddressInfo(encoded) + + if err != nil { + return nil, err + } + + if info.PubKey == nil { + return nil, fmt.Errorf("address %s has no public key", encoded) + } + + decodedHex, err := hex.DecodeString(*info.PubKey) if err != nil { return nil, err } - return privKey.PrivKey.PubKey(), nil + return btcec.ParsePubKey(decodedHex) } func (w *RpcWalletController) DumpPrivateKey(address btcutil.Address) (*btcec.PrivateKey, error) {