Skip to content

Commit

Permalink
Sign taproot thorugh psbt (#21)
Browse files Browse the repository at this point in the history
Sign taproot spends by `walletprocesspsbt` endpoint
  • Loading branch information
KonradStaniec authored Aug 12, 2024
1 parent 17fad51 commit 2645e95
Show file tree
Hide file tree
Showing 7 changed files with 186 additions and 60 deletions.
2 changes: 1 addition & 1 deletion itest/bitcoind_node_setup.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ func (h *BitcoindTestHandler) CreateWallet(walletName string, passphrase string)
// last false on the list will create legacy wallet. This is needed, as currently
// we are signing all taproot transactions by dumping the private key and signing it
// on app level. Descriptor wallets do not allow dumping private keys.
buff, _, err := h.m.ExecBitcoindCliCmd(h.t, []string{"createwallet", walletName, "false", "false", passphrase, "false", "false"})
buff, _, err := h.m.ExecBitcoindCliCmd(h.t, []string{"createwallet", walletName, "false", "false", passphrase})
require.NoError(h.t, err)

var response CreateWalletResponse
Expand Down
2 changes: 1 addition & 1 deletion itest/containers/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ type ImageConfig struct {
//nolint:deadcode
const (
dockerBitcoindRepository = "lncm/bitcoind"
dockerBitcoindVersionTag = "v24.0.1"
dockerBitcoindVersionTag = "v26.0"
)

// NewImageConfig returns ImageConfig needed for running e2e test.
Expand Down
88 changes: 57 additions & 31 deletions itest/e2e_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (

"github.com/babylonlabs-io/babylon/crypto/bip322"
btcctypes "github.com/babylonlabs-io/babylon/x/btccheckpoint/types"
"github.com/cometbft/cometbft/crypto/tmhash"

staking "github.com/babylonlabs-io/babylon/btcstaking"
txformat "github.com/babylonlabs-io/babylon/btctxformatter"
Expand Down Expand Up @@ -139,7 +140,7 @@ type TestManager struct {
Db kvdb.Backend
Sa *staker.StakerApp
BabylonClient *babylonclient.BabylonController
WalletPrivKey *btcec.PrivateKey
WalletPubKey *btcec.PublicKey
MinerAddr btcutil.Address
serverStopper *signal.Interceptor
wg *sync.WaitGroup
Expand Down Expand Up @@ -294,7 +295,13 @@ func StartManager(
err = walletClient.UnlockWallet(20)
require.NoError(t, err)

walletPrivKey, err := c.DumpPrivKey(minerAddressDecoded)
info, err := c.GetAddressInfo(br.Address)
require.NoError(t, err)

pubKeyHex := *info.PubKey
pubKeyBytes, err := hex.DecodeString(pubKeyHex)
require.NoError(t, err)
walletPubKey, err := btcec.ParsePubKey(pubKeyBytes)
require.NoError(t, err)

interceptor, err := signal.Intercept()
Expand Down Expand Up @@ -334,7 +341,7 @@ func StartManager(
Db: dbbackend,
Sa: stakerApp,
BabylonClient: bl,
WalletPrivKey: walletPrivKey.PrivKey,
WalletPubKey: walletPubKey,
MinerAddr: minerAddressDecoded,
serverStopper: &interceptor,
wg: &wg,
Expand Down Expand Up @@ -809,13 +816,20 @@ func (tm *TestManager) sendWatchedStakingTx(
stakingTxSlashingPathInfo, err := stakingInfo.SlashingPathSpendInfo()
require.NoError(t, err)

slashSig, err := staking.SignTxWithOneScriptSpendInputFromScript(
slashingTx,
tx.TxOut[stakingOutputIdx],
tm.WalletPrivKey,
stakingTxSlashingPathInfo.RevealedLeaf.Script,
slashingSigResult, err := tm.Sa.Wallet().SignOneInputTaprootSpendingTransaction(
&walletcontroller.TaprootSigningRequest{
FundingOutput: stakingInfo.StakingOutput,
TxToSign: slashingTx,
SignerAddress: tm.MinerAddr,
SpendDescription: &walletcontroller.SpendPathDescription{
ControlBlock: &stakingTxSlashingPathInfo.ControlBlock,
ScriptLeaf: &stakingTxSlashingPathInfo.RevealedLeaf,
},
},
)

require.NoError(t, err)
require.NotNil(t, slashingSigResult.Signature)

serializedStakingTx, err := utils.SerializeBtcTransaction(tx)
require.NoError(t, err)
Expand Down Expand Up @@ -855,23 +869,35 @@ func (tm *TestManager) sendWatchedStakingTx(
)
require.NoError(t, err)

slashUnbondingSig, err := staking.SignTxWithOneScriptSpendInputFromScript(
slashUnbondingTx,
unbondingTx.TxOut[0],
tm.WalletPrivKey,
unbondingSlashingPathInfo.RevealedLeaf.Script,
slashingUnbondingSigResult, err := tm.Sa.Wallet().SignOneInputTaprootSpendingTransaction(
&walletcontroller.TaprootSigningRequest{
FundingOutput: unbondingTx.TxOut[0],
TxToSign: slashUnbondingTx,
SignerAddress: tm.MinerAddr,
SpendDescription: &walletcontroller.SpendPathDescription{
ControlBlock: &unbondingSlashingPathInfo.ControlBlock,
ScriptLeaf: &unbondingSlashingPathInfo.RevealedLeaf,
},
},
)

require.NoError(t, err)
require.NotNil(t, slashingUnbondingSigResult.Signature)

serializedUnbondingTx, err := utils.SerializeBtcTransaction(unbondingTx)
require.NoError(t, err)
serializedSlashUnbondingTx, err := utils.SerializeBtcTransaction(slashUnbondingTx)
require.NoError(t, err)

// TODO: Update pop when new version will be ready, for now using schnorr as we don't have
// easy way to generate bip322 sig on backend side
pop, err := btcstypes.NewPoPBTC(
testStakingData.StakerBabylonAddr,
tm.WalletPrivKey,
babylonAddrHash := tmhash.Sum(testStakingData.StakerBabylonAddr.Bytes())

sig, err := tm.Sa.Wallet().SignBip322NativeSegwit(babylonAddrHash, tm.MinerAddr)
require.NoError(t, err)

pop, err := babylonclient.NewBabylonBip322Pop(
babylonAddrHash,
sig,
tm.MinerAddr,
)
require.NoError(t, err)

Expand All @@ -888,16 +914,16 @@ func (tm *TestManager) sendWatchedStakingTx(
hex.EncodeToString(schnorr.SerializePubKey(testStakingData.StakerKey)),
fpBTCPKs,
hex.EncodeToString(serializedSlashingTx),
hex.EncodeToString(slashSig.Serialize()),
hex.EncodeToString(slashingSigResult.Signature.Serialize()),
testStakingData.StakerBabylonAddr.String(),
tm.MinerAddr.String(),
hex.EncodeToString(pop.BtcSig),
hex.EncodeToString(serializedUnbondingTx),
hex.EncodeToString(serializedSlashUnbondingTx),
hex.EncodeToString(slashUnbondingSig.Serialize()),
hex.EncodeToString(slashingUnbondingSigResult.Signature.Serialize()),
int(unbondingTme),
// Use schnor verification
int(btcstypes.BTCSigType_BIP340),
int(btcstypes.BTCSigType_BIP322),
)
require.NoError(t, err)

Expand Down Expand Up @@ -1077,7 +1103,7 @@ func TestStakingFailures(t *testing.T) {
require.NoError(t, err)
stakingTime := uint16(staker.GetMinStakingTime(params))

testStakingData := tm.getTestStakingData(t, tm.WalletPrivKey.PubKey(), stakingTime, 10000, 1)
testStakingData := tm.getTestStakingData(t, tm.WalletPubKey, stakingTime, 10000, 1)
fpKey := hex.EncodeToString(schnorr.SerializePubKey(testStakingData.FinalityProviderBtcKeys[0]))

tm.createAndRegisterFinalityProviders(t, testStakingData)
Expand Down Expand Up @@ -1117,7 +1143,7 @@ func TestSendingStakingTransaction(t *testing.T) {
require.NoError(t, err)
stakingTime := uint16(staker.GetMinStakingTime(params))

testStakingData := tm.getTestStakingData(t, tm.WalletPrivKey.PubKey(), stakingTime, 10000, 1)
testStakingData := tm.getTestStakingData(t, tm.WalletPubKey, stakingTime, 10000, 1)

hashed, err := chainhash.NewHash(datagen.GenRandomByteArray(r, 32))
require.NoError(t, err)
Expand Down Expand Up @@ -1197,7 +1223,7 @@ func TestMultipleWithdrawableStakingTransactions(t *testing.T) {
stakingTime4 := minStakingTime + 2
stakingTime5 := minStakingTime + 3

testStakingData1 := tm.getTestStakingData(t, tm.WalletPrivKey.PubKey(), stakingTime1, 10000, 1)
testStakingData1 := tm.getTestStakingData(t, tm.WalletPubKey, stakingTime1, 10000, 1)
testStakingData2 := testStakingData1.withStakingTime(stakingTime2)
testStakingData3 := testStakingData1.withStakingTime(stakingTime3)
testStakingData4 := testStakingData1.withStakingTime(stakingTime4)
Expand Down Expand Up @@ -1257,7 +1283,7 @@ func TestSendingWatchedStakingTransaction(t *testing.T) {
params, err := cl.Params()
require.NoError(t, err)
stakingTime := uint16(staker.GetMinStakingTime(params))
testStakingData := tm.getTestStakingData(t, tm.WalletPrivKey.PubKey(), stakingTime, 10000, 1)
testStakingData := tm.getTestStakingData(t, tm.WalletPubKey, stakingTime, 10000, 1)

tm.createAndRegisterFinalityProviders(t, testStakingData)

Expand All @@ -1279,7 +1305,7 @@ func TestRestartingTxNotDeepEnough(t *testing.T) {
params, err := cl.Params()
require.NoError(t, err)
stakingTime := uint16(staker.GetMinStakingTime(params))
testStakingData := tm.getTestStakingData(t, tm.WalletPrivKey.PubKey(), stakingTime, 10000, 1)
testStakingData := tm.getTestStakingData(t, tm.WalletPubKey, stakingTime, 10000, 1)

tm.createAndRegisterFinalityProviders(t, testStakingData)
txHash := tm.sendStakingTxBTC(t, testStakingData)
Expand All @@ -1305,7 +1331,7 @@ func TestRestartingTxNotOnBabylon(t *testing.T) {
require.NoError(t, err)
stakingTime := uint16(staker.GetMinStakingTime(params))

testStakingData1 := tm.getTestStakingData(t, tm.WalletPrivKey.PubKey(), stakingTime, 10000, 1)
testStakingData1 := tm.getTestStakingData(t, tm.WalletPubKey, stakingTime, 10000, 1)
testStakingData2 := testStakingData1.withStakingAmout(11000)

tm.createAndRegisterFinalityProviders(t, testStakingData1)
Expand Down Expand Up @@ -1347,7 +1373,7 @@ func TestStakingUnbonding(t *testing.T) {
require.NoError(t, err)
// large staking time
stakingTime := uint16(1000)
testStakingData := tm.getTestStakingData(t, tm.WalletPrivKey.PubKey(), stakingTime, 50000, 1)
testStakingData := tm.getTestStakingData(t, tm.WalletPubKey, stakingTime, 50000, 1)

tm.createAndRegisterFinalityProviders(t, testStakingData)

Expand Down Expand Up @@ -1418,7 +1444,7 @@ func TestUnbondingRestartWaitingForSignatures(t *testing.T) {
require.NoError(t, err)
// large staking time
stakingTime := uint16(1000)
testStakingData := tm.getTestStakingData(t, tm.WalletPrivKey.PubKey(), stakingTime, 50000, 1)
testStakingData := tm.getTestStakingData(t, tm.WalletPubKey, stakingTime, 50000, 1)

tm.createAndRegisterFinalityProviders(t, testStakingData)

Expand Down Expand Up @@ -1588,7 +1614,7 @@ func TestSendingStakingTransaction_Restaking(t *testing.T) {
stakingTime := uint16(staker.GetMinStakingTime(params))

// restaked to 5 finality providers
testStakingData := tm.getTestStakingData(t, tm.WalletPrivKey.PubKey(), stakingTime, 10000, 5)
testStakingData := tm.getTestStakingData(t, tm.WalletPubKey, stakingTime, 10000, 5)

hashed, err := chainhash.NewHash(datagen.GenRandomByteArray(r, 32))
require.NoError(t, err)
Expand Down Expand Up @@ -1628,7 +1654,7 @@ func TestRecoverAfterRestartDuringWithdrawal(t *testing.T) {
require.NoError(t, err)
stakingTime := uint16(staker.GetMinStakingTime(params))

testStakingData := tm.getTestStakingData(t, tm.WalletPrivKey.PubKey(), stakingTime, 10000, 1)
testStakingData := tm.getTestStakingData(t, tm.WalletPubKey, stakingTime, 10000, 1)

hashed, err := chainhash.NewHash(datagen.GenRandomByteArray(r, 32))
require.NoError(t, err)
Expand Down
12 changes: 10 additions & 2 deletions staker/babylontypes.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,10 @@ func (app *StakerApp) buildOwnedDelegation(
return nil, fmt.Errorf("error signing slashing transaction for staking transaction: %w", err)
}

if stakingSlashingSig.Signature == nil {
return nil, fmt.Errorf("failed to receive stakingSlashingSig.Signature ")
}

unbondingSlashingSig, err := app.signTaprootScriptSpendUsingWallet(
undelegationDesc.SlashUnbondingTransaction,
undelegationDesc.UnbondingTransaction.TxOut[0],
Expand All @@ -103,21 +107,25 @@ func (app *StakerApp) buildOwnedDelegation(
return nil, fmt.Errorf("error signing slashing transaction for unbonding transaction: %w", err)
}

if unbondingSlashingSig.Signature == nil {
return nil, fmt.Errorf("failed to receive unbondingSlashingSig.Signature ")
}

dg := createDelegationData(
externalData.stakerPublicKey,
req.inclusionBlock,
req.txIndex,
storedTx,
stakingSlashingTx,
stakingSlashingSig,
stakingSlashingSig.Signature,
externalData.babylonStakerAddr,
stakingTxInclusionProof,
&cl.UndelegationData{
UnbondingTransaction: undelegationDesc.UnbondingTransaction,
UnbondingTxValue: undelegationDesc.UnbondingTxValue,
UnbondingTxUnbondingTime: undelegationDesc.UnbondingTxUnbondingTime,
SlashUnbondingTransaction: undelegationDesc.SlashUnbondingTransaction,
SlashUnbondingTransactionSig: unbondingSlashingSig,
SlashUnbondingTransactionSig: unbondingSlashingSig.Signature,
},
)

Expand Down
18 changes: 11 additions & 7 deletions staker/stakerapp.go
Original file line number Diff line number Diff line change
Expand Up @@ -944,14 +944,18 @@ func (app *StakerApp) sendUnbondingTxToBtcWithWitness(
return fmt.Errorf("failed to send unbondingtx. wallet signing error: %w", err)
}

if stakerUnbondingSig.Signature == nil {
return fmt.Errorf("failed to receive stakerUnbondingSig.Signature")
}

covenantSigantures := createWitnessSignaturesForPubKeys(
params.CovenantPks,
unbondingData.CovenantSignatures,
)

witness, err := unbondingSpendInfo.CreateUnbondingPathWitness(
covenantSigantures,
stakerUnbondingSig,
stakerUnbondingSig.Signature,
)

if err != nil {
Expand Down Expand Up @@ -1720,7 +1724,7 @@ func (app *StakerApp) signTaprootScriptSpendUsingWallet(
signerAddress btcutil.Address,
leaf *txscript.TapLeaf,
controlBlock *txscript.ControlBlock,
) (*schnorr.Signature, error) {
) (*walletcontroller.TaprootSigningResult, error) {

if err := app.wc.UnlockWallet(defaultWalletUnlockTimeout); err != nil {
return nil, fmt.Errorf("failed to unlock wallet before signing: %w", err)
Expand All @@ -1742,7 +1746,7 @@ func (app *StakerApp) signTaprootScriptSpendUsingWallet(
return nil, err
}

return resp.Signature, nil
return resp, nil
}

// SpendStake spends stake identified by stakingTxHash. Stake can be currently locked in
Expand Down Expand Up @@ -1832,15 +1836,15 @@ func (app *StakerApp) SpendStake(stakingTxHash *chainhash.Hash) (*chainhash.Hash
return nil, nil, fmt.Errorf("cannot spend staking output. Error building signature: %w", err)
}

witness, err := spendStakeTxInfo.fundingOutputSpendInfo.CreateTimeLockPathWitness(
stakerSig,
)
if stakerSig.FullInputWitness == nil {
return nil, nil, fmt.Errorf("failed to recevie full witness to spend staking transactions")
}

if err != nil {
return nil, nil, fmt.Errorf("cannot spend staking output. Error building witness: %w", err)
}

spendStakeTxInfo.spendStakeTx.TxIn[0].Witness = witness
spendStakeTxInfo.spendStakeTx.TxIn[0].Witness = stakerSig.FullInputWitness

// We do not check if transaction is spendable i.e the staking time has passed
// as this is validated in mempool so in of not meeting this time requirement
Expand Down
Loading

0 comments on commit 2645e95

Please sign in to comment.