From a952999b68b3833e543c73146afd83eb1064680e Mon Sep 17 00:00:00 2001 From: RafilxTenfen Date: Wed, 9 Oct 2024 12:38:34 -0300 Subject: [PATCH 01/17] chore: simplified equal pk check --- covenant/covenant.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/covenant/covenant.go b/covenant/covenant.go index 8fdfa51..145c8b0 100644 --- a/covenant/covenant.go +++ b/covenant/covenant.go @@ -1,7 +1,6 @@ package covenant import ( - "bytes" "encoding/hex" "fmt" "strings" @@ -502,7 +501,7 @@ func (ce *CovenantEmulator) removeAlreadySigned(dels []*types.Delegation) []*typ delCopy := del alreadySigned := false for _, covSig := range delCopy.CovenantSigs { - if bytes.Equal(schnorr.SerializePubKey(covSig.Pk), schnorr.SerializePubKey(ce.pk)) { + if covSig.Pk.IsEqual(ce.pk) { alreadySigned = true break } From 8544c67c95e5498e8deed170d1d0a3a7bde19ba0 Mon Sep 17 00:00:00 2001 From: RafilxTenfen Date: Wed, 9 Oct 2024 16:36:11 -0300 Subject: [PATCH 02/17] feat: add crypto signer wrapper for btcstaking signing functions --- cmd/covd/key.go | 4 +- cmd/covd/start.go | 14 +++- covenant/covenant.go | 103 +++++---------------------- covenant/covenant_test.go | 11 ++- covenant/expected_interfaces.go | 38 ++++++++++ itest/test_manager.go | 9 ++- keyring/keyring.go | 22 ++++++ keyring/signer.go | 120 ++++++++++++++++++++++++++++++++ 8 files changed, 228 insertions(+), 93 deletions(-) create mode 100644 covenant/expected_interfaces.go create mode 100644 keyring/signer.go diff --git a/cmd/covd/key.go b/cmd/covd/key.go index bb6633f..84e92f2 100644 --- a/cmd/covd/key.go +++ b/cmd/covd/key.go @@ -9,7 +9,7 @@ import ( "github.com/urfave/cli" covcfg "github.com/babylonlabs-io/covenant-emulator/config" - "github.com/babylonlabs-io/covenant-emulator/covenant" + "github.com/babylonlabs-io/covenant-emulator/keyring" ) type covenantKey struct { @@ -71,7 +71,7 @@ func createKey(ctx *cli.Context) error { return fmt.Errorf("failed to load the config from %s: %w", covcfg.ConfigFile(homePath), err) } - keyPair, err := covenant.CreateCovenantKey( + keyPair, err := keyring.CreateCovenantKey( homePath, chainID, keyName, diff --git a/cmd/covd/start.go b/cmd/covd/start.go index 1256f61..6c9ee3d 100644 --- a/cmd/covd/start.go +++ b/cmd/covd/start.go @@ -5,6 +5,7 @@ import ( "path/filepath" covcfg "github.com/babylonlabs-io/covenant-emulator/config" + "github.com/babylonlabs-io/covenant-emulator/keyring" "github.com/babylonlabs-io/covenant-emulator/log" "github.com/babylonlabs-io/covenant-emulator/util" @@ -57,7 +58,14 @@ func start(ctx *cli.Context) error { return fmt.Errorf("failed to create rpc client for the consumer chain: %w", err) } - ce, err := covenant.NewCovenantEmulator(cfg, bbnClient, ctx.String(passphraseFlag), logger) + pwd := ctx.String(passphraseFlag) + + signer, err := NewSignerFromConfig(cfg, pwd) + if err != nil { + return fmt.Errorf("failed to create signer from config: %w", err) + } + + ce, err := covenant.NewCovenantEmulator(cfg, bbnClient, pwd, logger, signer) if err != nil { return fmt.Errorf("failed to start the covenant emulator: %w", err) } @@ -75,3 +83,7 @@ func start(ctx *cli.Context) error { return srv.RunUntilShutdown() } + +func NewSignerFromConfig(cfg *covcfg.Config, passphrase string) (*keyring.KeyringCriptoSigner, error) { + return keyring.NewLocalKeyringCriptoSigner(cfg.BabylonConfig.ChainID, cfg.BabylonConfig.Key, cfg.BabylonConfig.KeyDirectory, cfg.BabylonConfig.KeyringBackend, passphrase) +} diff --git a/covenant/covenant.go b/covenant/covenant.go index 145c8b0..76696c5 100644 --- a/covenant/covenant.go +++ b/covenant/covenant.go @@ -3,7 +3,6 @@ package covenant import ( "encoding/hex" "fmt" - "strings" "sync" "time" @@ -21,7 +20,6 @@ import ( "github.com/babylonlabs-io/covenant-emulator/clientcontroller" covcfg "github.com/babylonlabs-io/covenant-emulator/config" - "github.com/babylonlabs-io/covenant-emulator/keyring" "github.com/babylonlabs-io/covenant-emulator/types" ) @@ -42,15 +40,11 @@ type CovenantEmulator struct { pk *btcec.PublicKey - cc clientcontroller.ClientController - kc *keyring.ChainKeyringController + signer CriptoSigner + cc clientcontroller.ClientController config *covcfg.Config logger *zap.Logger - - // input is used to pass passphrase to the keyring - input *strings.Reader - passphrase string } func NewCovenantEmulator( @@ -58,42 +52,20 @@ func NewCovenantEmulator( cc clientcontroller.ClientController, passphrase string, logger *zap.Logger, + signer CriptoSigner, ) (*CovenantEmulator, error) { - input := strings.NewReader("") - kr, err := keyring.CreateKeyring( - config.BabylonConfig.KeyDirectory, - config.BabylonConfig.ChainID, - config.BabylonConfig.KeyringBackend, - input, - ) - if err != nil { - return nil, fmt.Errorf("failed to create keyring: %w", err) - } - - kc, err := keyring.NewChainKeyringControllerWithKeyring(kr, config.BabylonConfig.Key, input) - if err != nil { - return nil, err - } - - sk, err := kc.GetChainPrivKey(passphrase) + pk, err := signer.PubKey() if err != nil { - return nil, fmt.Errorf("covenant key %s is not found: %w", config.BabylonConfig.Key, err) - } - - pk, err := btcec.ParsePubKey(sk.PubKey().Bytes()) - if err != nil { - return nil, err + return nil, fmt.Errorf("failed to get signer pub key: %w", err) } return &CovenantEmulator{ - cc: cc, - kc: kc, - config: config, - logger: logger, - input: input, - passphrase: passphrase, - pk: pk, - quit: make(chan struct{}), + cc: cc, + signer: signer, + config: config, + logger: logger, + pk: pk, + quit: make(chan struct{}), }, nil } @@ -219,17 +191,12 @@ func (ce *CovenantEmulator) AddCovenantSignatures(btcDels []*types.Delegation) ( startSignTime := time.Now() metricsTimeKeeper.SetPreviousSignStart(&startSignTime) - covenantPrivKey, err := ce.getPrivKey() - if err != nil { - return nil, fmt.Errorf("failed to get Covenant private key: %w", err) - } - slashSigs, unbondingSig, err := signSlashAndUnbondSignatures( btcDel, stakingTx, slashingTx, unbondingTx, - covenantPrivKey, + ce.signer, params, &ce.config.BTCNetParams, ) @@ -243,7 +210,7 @@ func (ce *CovenantEmulator) AddCovenantSignatures(btcDels []*types.Delegation) ( btcDel, unbondingTx, slashUnbondingTx, - covenantPrivKey, + ce.signer, params, &ce.config.BTCNetParams, ) @@ -286,7 +253,7 @@ func signSlashUnbondingSignatures( del *types.Delegation, unbondingTx *wire.MsgTx, slashUnbondingTx *wire.MsgTx, - covPrivKey *btcec.PrivateKey, + signer CriptoSigner, params *types.StakingParams, btcNet *chaincfg.Params, ) ([][]byte, error) { @@ -314,12 +281,11 @@ func signSlashUnbondingSignatures( if err != nil { return nil, err } - slashUnbondingSig, err := btcstaking.EncSignTxWithOneScriptSpendInputStrict( + slashUnbondingSig, err := signer.EncSignTxWithOneScriptSpendInputStrict( slashUnbondingTx, unbondingTx, 0, // 0th output is always the unbonding script output unbondingTxSlashingPath.GetPkScriptPath(), - covPrivKey, encKey, ) if err != nil { @@ -336,7 +302,7 @@ func signSlashAndUnbondSignatures( stakingTx *wire.MsgTx, slashingTx *wire.MsgTx, unbondingTx *wire.MsgTx, - covPrivKey *btcec.PrivateKey, + signer CriptoSigner, params *types.StakingParams, btcNet *chaincfg.Params, ) ([][]byte, *schnorr.Signature, error) { @@ -367,12 +333,11 @@ func signSlashAndUnbondSignatures( return nil, nil, fmt.Errorf("failed to get encryption key from finality provider public key %s: %w", fpPkHex, err) } - slashSig, err := btcstaking.EncSignTxWithOneScriptSpendInputStrict( + slashSig, err := signer.EncSignTxWithOneScriptSpendInputStrict( slashingTx, stakingTx, del.StakingOutputIdx, slashingPathInfo.GetPkScriptPath(), - covPrivKey, encKey, ) if err != nil { @@ -387,12 +352,11 @@ func signSlashAndUnbondSignatures( if err != nil { return nil, nil, fmt.Errorf("failed to get unbonding path spend info") } - unbondingSig, err := btcstaking.SignTxWithOneScriptSpendInputStrict( + unbondingSig, err := signer.SignTxWithOneScriptSpendInputStrict( unbondingTx, stakingTx, del.StakingOutputIdx, stakingTxUnbondingPathInfo.GetPkScriptPath(), - covPrivKey, ) if err != nil { return nil, nil, fmt.Errorf("failed to sign unbonding tx: %w", err) @@ -466,17 +430,6 @@ func decodeUndelegationTransactions(del *types.Delegation, params *types.Staking return unbondingMsgTx, unbondingSlashingMsgTx, err } -func (ce *CovenantEmulator) getPrivKey() (*btcec.PrivateKey, error) { - sdkPrivKey, err := ce.kc.GetChainPrivKey(ce.passphrase) - if err != nil { - return nil, err - } - - privKey, _ := btcec.PrivKeyFromBytes(sdkPrivKey.Key) - - return privKey, nil -} - // delegationsToBatches takes a list of delegations and splits them into batches func (ce *CovenantEmulator) delegationsToBatches(dels []*types.Delegation) [][]*types.Delegation { batchSize := ce.config.SigsBatchSize @@ -583,26 +536,6 @@ func (ce *CovenantEmulator) metricsUpdateLoop() { } } -func CreateCovenantKey(keyringDir, chainID, keyName, backend, passphrase, hdPath string) (*types.ChainKeyInfo, error) { - sdkCtx, err := keyring.CreateClientCtx( - keyringDir, chainID, - ) - if err != nil { - return nil, err - } - - krController, err := keyring.NewChainKeyringController( - sdkCtx, - keyName, - backend, - ) - if err != nil { - return nil, err - } - - return krController.CreateChainKey(passphrase, hdPath) -} - func (ce *CovenantEmulator) getParamsByVersionWithRetry(version uint32) (*types.StakingParams, error) { var ( params *types.StakingParams diff --git a/covenant/covenant_test.go b/covenant/covenant_test.go index 5ed2891..55d241e 100644 --- a/covenant/covenant_test.go +++ b/covenant/covenant_test.go @@ -2,10 +2,11 @@ package covenant_test import ( "encoding/hex" - "github.com/btcsuite/btcd/btcutil" "math/rand" "testing" + "github.com/btcsuite/btcd/btcutil" + "github.com/babylonlabs-io/babylon/btcstaking" asig "github.com/babylonlabs-io/babylon/crypto/schnorr-adaptor-signature" "github.com/babylonlabs-io/babylon/testutil/datagen" @@ -17,6 +18,7 @@ import ( covcfg "github.com/babylonlabs-io/covenant-emulator/config" "github.com/babylonlabs-io/covenant-emulator/covenant" + "github.com/babylonlabs-io/covenant-emulator/keyring" "github.com/babylonlabs-io/covenant-emulator/testutil" "github.com/babylonlabs-io/covenant-emulator/types" ) @@ -38,7 +40,7 @@ func FuzzAddCovenantSig(f *testing.F) { // create a Covenant key pair in the keyring covenantConfig := covcfg.DefaultConfig() - covKeyPair, err := covenant.CreateCovenantKey( + covKeyPair, err := keyring.CreateCovenantKey( covenantConfig.BabylonConfig.KeyDirectory, covenantConfig.BabylonConfig.ChainID, covenantConfig.BabylonConfig.Key, @@ -48,8 +50,11 @@ func FuzzAddCovenantSig(f *testing.F) { ) require.NoError(t, err) + signer, err := keyring.NewLocalKeyringCriptoSigner(covenantConfig.BabylonConfig.ChainID, covenantConfig.BabylonConfig.Key, covenantConfig.BabylonConfig.KeyDirectory, covenantConfig.BabylonConfig.KeyringBackend, passphrase) + require.NoError(t, err) + // create and start covenant emulator - ce, err := covenant.NewCovenantEmulator(&covenantConfig, mockClientController, passphrase, zap.NewNop()) + ce, err := covenant.NewCovenantEmulator(&covenantConfig, mockClientController, passphrase, zap.NewNop(), signer) require.NoError(t, err) numDels := datagen.RandomInt(r, 3) + 1 diff --git a/covenant/expected_interfaces.go b/covenant/expected_interfaces.go new file mode 100644 index 0000000..f9d32ce --- /dev/null +++ b/covenant/expected_interfaces.go @@ -0,0 +1,38 @@ +package covenant + +import ( + asig "github.com/babylonlabs-io/babylon/crypto/schnorr-adaptor-signature" + "github.com/btcsuite/btcd/btcec/v2/schnorr" + "github.com/btcsuite/btcd/wire" + secp "github.com/decred/dcrd/dcrec/secp256k1/v4" +) + +// CriptoSigner wrapper interface to sign messages +type CriptoSigner interface { + // PubKey returns the current secp256k1 public key + PubKey() (*secp.PublicKey, error) + // EncSignTxWithOneScriptSpendInputStrict is encrypted version of + // SignTxWithOneScriptSpendInputStrict with the output to be encrypted + // by an encryption key (adaptor signature) + EncSignTxWithOneScriptSpendInputStrict( + txToSign *wire.MsgTx, + fundingTx *wire.MsgTx, + fundingOutputIdx uint32, + signedScriptPath []byte, + encKey *asig.EncryptionKey, + ) (*asig.AdaptorSignature, error) + // SignTxWithOneScriptSpendInputStrict signs transaction with one input coming + // from script spend output with provided script. + // It checks: + // - txToSign is not nil + // - txToSign has exactly one input + // - fundingTx is not nil + // - fundingTx has one output committing to the provided script + // - txToSign input is pointing to the correct output in fundingTx + SignTxWithOneScriptSpendInputStrict( + txToSign *wire.MsgTx, + fundingTx *wire.MsgTx, + fundingOutputIdx uint32, + signedScriptPath []byte, + ) (*schnorr.Signature, error) +} diff --git a/itest/test_manager.go b/itest/test_manager.go index bc16e67..142398a 100644 --- a/itest/test_manager.go +++ b/itest/test_manager.go @@ -24,6 +24,7 @@ import ( covcc "github.com/babylonlabs-io/covenant-emulator/clientcontroller" covcfg "github.com/babylonlabs-io/covenant-emulator/config" "github.com/babylonlabs-io/covenant-emulator/covenant" + covdkeyring "github.com/babylonlabs-io/covenant-emulator/keyring" "github.com/babylonlabs-io/covenant-emulator/testutil" "github.com/babylonlabs-io/covenant-emulator/types" ) @@ -80,7 +81,7 @@ func StartManager(t *testing.T) *TestManager { covenantConfig := defaultCovenantConfig(testDir) err = covenantConfig.Validate() require.NoError(t, err) - covKeyPair, err := covenant.CreateCovenantKey(testDir, chainID, covenantKeyName, keyring.BackendTest, passphrase, hdPath) + covKeyPair, err := covdkeyring.CreateCovenantKey(testDir, chainID, covenantKeyName, keyring.BackendTest, passphrase, hdPath) require.NoError(t, err) // 2. prepare Babylon node @@ -92,7 +93,11 @@ func StartManager(t *testing.T) *TestManager { bbnCfg := defaultBBNConfigWithKey("test-spending-key", bh.GetNodeDataDir()) covbc, err := covcc.NewBabylonController(bbnCfg, &covenantConfig.BTCNetParams, logger) require.NoError(t, err) - ce, err := covenant.NewCovenantEmulator(covenantConfig, covbc, passphrase, logger) + + signer, err := covdkeyring.NewLocalKeyringCriptoSigner(covenantConfig.BabylonConfig.ChainID, covenantConfig.BabylonConfig.Key, covenantConfig.BabylonConfig.KeyDirectory, covenantConfig.BabylonConfig.KeyringBackend, passphrase) + require.NoError(t, err) + + ce, err := covenant.NewCovenantEmulator(covenantConfig, covbc, passphrase, logger, signer) require.NoError(t, err) err = ce.Start() require.NoError(t, err) diff --git a/keyring/keyring.go b/keyring/keyring.go index c1c914a..efefdd8 100644 --- a/keyring/keyring.go +++ b/keyring/keyring.go @@ -10,6 +10,7 @@ import ( "github.com/cosmos/cosmos-sdk/crypto/keyring" "github.com/babylonlabs-io/covenant-emulator/codec" + "github.com/babylonlabs-io/covenant-emulator/types" ) func CreateKeyring(keyringDir string, chainId string, backend string, input *strings.Reader) (keyring.Keyring, error) { @@ -52,3 +53,24 @@ func CreateClientCtx(keyringDir string, chainId string) (client.Context, error) WithCodec(codec.MakeCodec()). WithKeyringDir(keyringDir), nil } + +// CreateCovenantKey creates a new key inside the keyring +func CreateCovenantKey(keyringDir, chainID, keyName, backend, passphrase, hdPath string) (*types.ChainKeyInfo, error) { + sdkCtx, err := CreateClientCtx( + keyringDir, chainID, + ) + if err != nil { + return nil, err + } + + krController, err := NewChainKeyringController( + sdkCtx, + keyName, + backend, + ) + if err != nil { + return nil, err + } + + return krController.CreateChainKey(passphrase, hdPath) +} diff --git a/keyring/signer.go b/keyring/signer.go new file mode 100644 index 0000000..a779533 --- /dev/null +++ b/keyring/signer.go @@ -0,0 +1,120 @@ +package keyring + +import ( + "fmt" + "strings" + + "github.com/babylonlabs-io/babylon/btcstaking" + "github.com/babylonlabs-io/covenant-emulator/covenant" + + asig "github.com/babylonlabs-io/babylon/crypto/schnorr-adaptor-signature" + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcec/v2/schnorr" + "github.com/btcsuite/btcd/wire" + secp "github.com/decred/dcrd/dcrec/secp256k1/v4" +) + +var _ covenant.CriptoSigner = KeyringCriptoSigner{} + +type KeyringCriptoSigner struct { + kc *ChainKeyringController + passphrase string +} + +func NewLocalKeyringCriptoSigner(chainId, keyName, keyringDir, keyringBackend, passphrase string) (*KeyringCriptoSigner, error) { + input := strings.NewReader("") + kr, err := CreateKeyring(keyringDir, chainId, keyringBackend, input) + if err != nil { + return nil, fmt.Errorf("failed to create keyring: %w", err) + } + + kc, err := NewChainKeyringControllerWithKeyring(kr, keyName, input) + if err != nil { + return nil, err + } + + return &KeyringCriptoSigner{ + kc: kc, + passphrase: passphrase, + }, nil +} + +func (kcs KeyringCriptoSigner) PubKey() (*secp.PublicKey, error) { + record, err := kcs.kc.KeyRecord() + if err != nil { + return nil, err + } + + pubKey, err := record.GetPubKey() + if err != nil { + return nil, err + } + + return btcec.ParsePubKey(pubKey.Bytes()) +} + +// getPrivKey returns the keyring private key +// TODO: update btcstaking functions to avoid receiving private key as parameter +// and only sign it using the kcs.kc.GetKeyring().Sign() +func (kcs KeyringCriptoSigner) getPrivKey() (*btcec.PrivateKey, error) { + sdkPrivKey, err := kcs.kc.GetChainPrivKey(kcs.passphrase) + if err != nil { + return nil, err + } + + privKey, _ := btcec.PrivKeyFromBytes(sdkPrivKey.Key) + return privKey, nil +} + +// EncSignTxWithOneScriptSpendInputStrict is encrypted version of +// SignTxWithOneScriptSpendInputStrict with the output to be encrypted +// by an encryption key (adaptor signature) +func (kcs KeyringCriptoSigner) EncSignTxWithOneScriptSpendInputStrict( + txToSign *wire.MsgTx, + fundingTx *wire.MsgTx, + fundingOutputIdx uint32, + signedScriptPath []byte, + encKey *asig.EncryptionKey, +) (*asig.AdaptorSignature, error) { + covenantPrivKey, err := kcs.getPrivKey() + if err != nil { + return nil, fmt.Errorf("failed to get Covenant private key: %w", err) + } + + return btcstaking.EncSignTxWithOneScriptSpendInputStrict( + txToSign, + fundingTx, + fundingOutputIdx, + signedScriptPath, + covenantPrivKey, + encKey, + ) +} + +// SignTxWithOneScriptSpendInputStrict signs transaction with one input coming +// from script spend output with provided script. +// It checks: +// - txToSign is not nil +// - txToSign has exactly one input +// - fundingTx is not nil +// - fundingTx has one output committing to the provided script +// - txToSign input is pointing to the correct output in fundingTx +func (kcs KeyringCriptoSigner) SignTxWithOneScriptSpendInputStrict( + txToSign *wire.MsgTx, + fundingTx *wire.MsgTx, + fundingOutputIdx uint32, + signedScriptPath []byte, +) (*schnorr.Signature, error) { + covenantPrivKey, err := kcs.getPrivKey() + if err != nil { + return nil, fmt.Errorf("failed to get Covenant private key: %w", err) + } + + return btcstaking.SignTxWithOneScriptSpendInputStrict( + txToSign, + fundingTx, + fundingOutputIdx, + signedScriptPath, + covenantPrivKey, + ) +} From 16fe1e6adb6e0f35223d9a5e77b7076ca4ce0632 Mon Sep 17 00:00:00 2001 From: RafilxTenfen Date: Wed, 9 Oct 2024 16:36:39 -0300 Subject: [PATCH 03/17] chore: update fpName to keyName in ChainKeyringController --- keyring/keyringcontroller.go | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/keyring/keyringcontroller.go b/keyring/keyringcontroller.go index e6ae73f..0852947 100644 --- a/keyring/keyringcontroller.go +++ b/keyring/keyringcontroller.go @@ -19,8 +19,8 @@ const ( ) type ChainKeyringController struct { - kr keyring.Keyring - fpName string + kr keyring.Keyring + keyName string // input is to send passphrase to kr input *strings.Reader } @@ -47,9 +47,9 @@ func NewChainKeyringController(ctx client.Context, name, keyringBackend string) } return &ChainKeyringController{ - fpName: name, - kr: kr, - input: inputReader, + keyName: name, + kr: kr, + input: inputReader, }, nil } @@ -59,9 +59,9 @@ func NewChainKeyringControllerWithKeyring(kr keyring.Keyring, name string, input } return &ChainKeyringController{ - kr: kr, - fpName: name, - input: input, + kr: kr, + keyName: name, + input: input, }, nil } @@ -89,7 +89,7 @@ func (kc *ChainKeyringController) CreateChainKey(passphrase, hdPath string) (*ty // we need to repeat the passphrase to mock the reentry kc.input.Reset(passphrase + "\n" + passphrase) - record, err := kc.kr.NewAccount(kc.fpName, mnemonic, passphrase, hdPath, algo) + record, err := kc.kr.NewAccount(kc.keyName, mnemonic, passphrase, hdPath, algo) if err != nil { return nil, err } @@ -100,7 +100,7 @@ func (kc *ChainKeyringController) CreateChainKey(passphrase, hdPath string) (*ty case *sdksecp256k1.PrivKey: sk, pk := btcec.PrivKeyFromBytes(v.Key) return &types.ChainKeyInfo{ - Name: kc.fpName, + Name: kc.keyName, PublicKey: pk, PrivateKey: sk, Mnemonic: mnemonic, @@ -112,7 +112,7 @@ func (kc *ChainKeyringController) CreateChainKey(passphrase, hdPath string) (*ty func (kc *ChainKeyringController) GetChainPrivKey(passphrase string) (*sdksecp256k1.PrivKey, error) { kc.input.Reset(passphrase) - k, err := kc.kr.Key(kc.fpName) + k, err := kc.kr.Key(kc.keyName) if err != nil { return nil, fmt.Errorf("failed to get private key: %w", err) } @@ -126,3 +126,7 @@ func (kc *ChainKeyringController) GetChainPrivKey(passphrase string) (*sdksecp25 return nil, fmt.Errorf("unsupported key type in keyring") } } + +func (kc *ChainKeyringController) KeyRecord() (*keyring.Record, error) { + return kc.GetKeyring().Key(kc.keyName) +} From 539dfd5b24b508786dfb8a277a4e34dcf1c297bc Mon Sep 17 00:00:00 2001 From: RafilxTenfen Date: Thu, 10 Oct 2024 10:27:30 -0300 Subject: [PATCH 04/17] chore: address naming suggestions CriptoSigner -> Signer, KeyringCriptoSigner -> KeyringSigner, NewLocalKeyringCriptoSigner -> NewKeyringSigner --- cmd/covd/start.go | 4 ++-- covenant/covenant.go | 8 ++++---- covenant/covenant_test.go | 2 +- covenant/expected_interfaces.go | 4 ++-- itest/test_manager.go | 2 +- keyring/signer.go | 16 ++++++++-------- 6 files changed, 18 insertions(+), 18 deletions(-) diff --git a/cmd/covd/start.go b/cmd/covd/start.go index 6c9ee3d..cd800df 100644 --- a/cmd/covd/start.go +++ b/cmd/covd/start.go @@ -84,6 +84,6 @@ func start(ctx *cli.Context) error { return srv.RunUntilShutdown() } -func NewSignerFromConfig(cfg *covcfg.Config, passphrase string) (*keyring.KeyringCriptoSigner, error) { - return keyring.NewLocalKeyringCriptoSigner(cfg.BabylonConfig.ChainID, cfg.BabylonConfig.Key, cfg.BabylonConfig.KeyDirectory, cfg.BabylonConfig.KeyringBackend, passphrase) +func NewSignerFromConfig(cfg *covcfg.Config, passphrase string) (*keyring.KeyringSigner, error) { + return keyring.NewKeyringSigner(cfg.BabylonConfig.ChainID, cfg.BabylonConfig.Key, cfg.BabylonConfig.KeyDirectory, cfg.BabylonConfig.KeyringBackend, passphrase) } diff --git a/covenant/covenant.go b/covenant/covenant.go index 76696c5..fa3a6f1 100644 --- a/covenant/covenant.go +++ b/covenant/covenant.go @@ -40,7 +40,7 @@ type CovenantEmulator struct { pk *btcec.PublicKey - signer CriptoSigner + signer Signer cc clientcontroller.ClientController config *covcfg.Config @@ -52,7 +52,7 @@ func NewCovenantEmulator( cc clientcontroller.ClientController, passphrase string, logger *zap.Logger, - signer CriptoSigner, + signer Signer, ) (*CovenantEmulator, error) { pk, err := signer.PubKey() if err != nil { @@ -253,7 +253,7 @@ func signSlashUnbondingSignatures( del *types.Delegation, unbondingTx *wire.MsgTx, slashUnbondingTx *wire.MsgTx, - signer CriptoSigner, + signer Signer, params *types.StakingParams, btcNet *chaincfg.Params, ) ([][]byte, error) { @@ -302,7 +302,7 @@ func signSlashAndUnbondSignatures( stakingTx *wire.MsgTx, slashingTx *wire.MsgTx, unbondingTx *wire.MsgTx, - signer CriptoSigner, + signer Signer, params *types.StakingParams, btcNet *chaincfg.Params, ) ([][]byte, *schnorr.Signature, error) { diff --git a/covenant/covenant_test.go b/covenant/covenant_test.go index 55d241e..795f4b5 100644 --- a/covenant/covenant_test.go +++ b/covenant/covenant_test.go @@ -50,7 +50,7 @@ func FuzzAddCovenantSig(f *testing.F) { ) require.NoError(t, err) - signer, err := keyring.NewLocalKeyringCriptoSigner(covenantConfig.BabylonConfig.ChainID, covenantConfig.BabylonConfig.Key, covenantConfig.BabylonConfig.KeyDirectory, covenantConfig.BabylonConfig.KeyringBackend, passphrase) + signer, err := keyring.NewKeyringSigner(covenantConfig.BabylonConfig.ChainID, covenantConfig.BabylonConfig.Key, covenantConfig.BabylonConfig.KeyDirectory, covenantConfig.BabylonConfig.KeyringBackend, passphrase) require.NoError(t, err) // create and start covenant emulator diff --git a/covenant/expected_interfaces.go b/covenant/expected_interfaces.go index f9d32ce..1040a0e 100644 --- a/covenant/expected_interfaces.go +++ b/covenant/expected_interfaces.go @@ -7,8 +7,8 @@ import ( secp "github.com/decred/dcrd/dcrec/secp256k1/v4" ) -// CriptoSigner wrapper interface to sign messages -type CriptoSigner interface { +// Signer wrapper interface to sign messages +type Signer interface { // PubKey returns the current secp256k1 public key PubKey() (*secp.PublicKey, error) // EncSignTxWithOneScriptSpendInputStrict is encrypted version of diff --git a/itest/test_manager.go b/itest/test_manager.go index 142398a..525840b 100644 --- a/itest/test_manager.go +++ b/itest/test_manager.go @@ -94,7 +94,7 @@ func StartManager(t *testing.T) *TestManager { covbc, err := covcc.NewBabylonController(bbnCfg, &covenantConfig.BTCNetParams, logger) require.NoError(t, err) - signer, err := covdkeyring.NewLocalKeyringCriptoSigner(covenantConfig.BabylonConfig.ChainID, covenantConfig.BabylonConfig.Key, covenantConfig.BabylonConfig.KeyDirectory, covenantConfig.BabylonConfig.KeyringBackend, passphrase) + signer, err := covdkeyring.NewKeyringSigner(covenantConfig.BabylonConfig.ChainID, covenantConfig.BabylonConfig.Key, covenantConfig.BabylonConfig.KeyDirectory, covenantConfig.BabylonConfig.KeyringBackend, passphrase) require.NoError(t, err) ce, err := covenant.NewCovenantEmulator(covenantConfig, covbc, passphrase, logger, signer) diff --git a/keyring/signer.go b/keyring/signer.go index a779533..0b13a03 100644 --- a/keyring/signer.go +++ b/keyring/signer.go @@ -14,14 +14,14 @@ import ( secp "github.com/decred/dcrd/dcrec/secp256k1/v4" ) -var _ covenant.CriptoSigner = KeyringCriptoSigner{} +var _ covenant.Signer = KeyringSigner{} -type KeyringCriptoSigner struct { +type KeyringSigner struct { kc *ChainKeyringController passphrase string } -func NewLocalKeyringCriptoSigner(chainId, keyName, keyringDir, keyringBackend, passphrase string) (*KeyringCriptoSigner, error) { +func NewKeyringSigner(chainId, keyName, keyringDir, keyringBackend, passphrase string) (*KeyringSigner, error) { input := strings.NewReader("") kr, err := CreateKeyring(keyringDir, chainId, keyringBackend, input) if err != nil { @@ -33,13 +33,13 @@ func NewLocalKeyringCriptoSigner(chainId, keyName, keyringDir, keyringBackend, p return nil, err } - return &KeyringCriptoSigner{ + return &KeyringSigner{ kc: kc, passphrase: passphrase, }, nil } -func (kcs KeyringCriptoSigner) PubKey() (*secp.PublicKey, error) { +func (kcs KeyringSigner) PubKey() (*secp.PublicKey, error) { record, err := kcs.kc.KeyRecord() if err != nil { return nil, err @@ -56,7 +56,7 @@ func (kcs KeyringCriptoSigner) PubKey() (*secp.PublicKey, error) { // getPrivKey returns the keyring private key // TODO: update btcstaking functions to avoid receiving private key as parameter // and only sign it using the kcs.kc.GetKeyring().Sign() -func (kcs KeyringCriptoSigner) getPrivKey() (*btcec.PrivateKey, error) { +func (kcs KeyringSigner) getPrivKey() (*btcec.PrivateKey, error) { sdkPrivKey, err := kcs.kc.GetChainPrivKey(kcs.passphrase) if err != nil { return nil, err @@ -69,7 +69,7 @@ func (kcs KeyringCriptoSigner) getPrivKey() (*btcec.PrivateKey, error) { // EncSignTxWithOneScriptSpendInputStrict is encrypted version of // SignTxWithOneScriptSpendInputStrict with the output to be encrypted // by an encryption key (adaptor signature) -func (kcs KeyringCriptoSigner) EncSignTxWithOneScriptSpendInputStrict( +func (kcs KeyringSigner) EncSignTxWithOneScriptSpendInputStrict( txToSign *wire.MsgTx, fundingTx *wire.MsgTx, fundingOutputIdx uint32, @@ -99,7 +99,7 @@ func (kcs KeyringCriptoSigner) EncSignTxWithOneScriptSpendInputStrict( // - fundingTx is not nil // - fundingTx has one output committing to the provided script // - txToSign input is pointing to the correct output in fundingTx -func (kcs KeyringCriptoSigner) SignTxWithOneScriptSpendInputStrict( +func (kcs KeyringSigner) SignTxWithOneScriptSpendInputStrict( txToSign *wire.MsgTx, fundingTx *wire.MsgTx, fundingOutputIdx uint32, From 445c8ef32771591da62be347ea996be6fbc65bf7 Mon Sep 17 00:00:00 2001 From: RafilxTenfen Date: Thu, 10 Oct 2024 10:41:39 -0300 Subject: [PATCH 05/17] chore: add changelog #20 --- CHANGELOG.md | 43 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..3083feb --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,43 @@ + + +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) + +## Unreleased + +### Improvements + +* [#20](https://github.com/babylonlabs-io/covenant-emulator/pull/20) Add signing behind +interface. From 185e43f9cc8204b97e59f4f459b538b5ab636b8b Mon Sep 17 00:00:00 2001 From: RafilxTenfen Date: Thu, 10 Oct 2024 15:03:23 -0300 Subject: [PATCH 06/17] feat: partial refactory in generating signatures --- covenant/covenant.go | 200 ++++++++++++++++++++++++++------ covenant/expected_interfaces.go | 28 +++++ keyring/signer.go | 70 +++++++++++ 3 files changed, 260 insertions(+), 38 deletions(-) diff --git a/covenant/covenant.go b/covenant/covenant.go index fa3a6f1..0b4a6de 100644 --- a/covenant/covenant.go +++ b/covenant/covenant.go @@ -15,6 +15,7 @@ import ( "github.com/btcsuite/btcd/btcec/v2/schnorr" "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg" + "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/wire" "go.uber.org/zap" @@ -84,7 +85,8 @@ func (ce *CovenantEmulator) AddCovenantSignatures(btcDels []*types.Delegation) ( if len(btcDels) == 0 { return nil, fmt.Errorf("no delegations") } - covenantSigs := make([]*types.CovenantSigs, 0, len(btcDels)) + + signingReq := make(map[chainhash.Hash]SigningTxsRequest) for _, btcDel := range btcDels { // 0. nil checks if btcDel == nil { @@ -177,7 +179,6 @@ func (ce *CovenantEmulator) AddCovenantSignatures(btcDels []*types.Delegation) ( // 7. Check unbonding fee unbondingFee := stakingTx.TxOut[btcDel.StakingOutputIdx].Value - unbondingTx.TxOut[0].Value - if unbondingFee != int64(params.UnbondingFee) { ce.logger.Error("invalid unbonding fee", zap.Int64("expected_unbonding_fee", int64(params.UnbondingFee)), @@ -186,54 +187,113 @@ func (ce *CovenantEmulator) AddCovenantSignatures(btcDels []*types.Delegation) ( continue } - // 8. sign covenant staking sigs - // record metrics - startSignTime := time.Now() - metricsTimeKeeper.SetPreviousSignStart(&startSignTime) + // Generate signing request data. - slashSigs, unbondingSig, err := signSlashAndUnbondSignatures( - btcDel, - stakingTx, - slashingTx, - unbondingTx, - ce.signer, - params, - &ce.config.BTCNetParams, - ) + fpsEncKeys := make([]*asig.EncryptionKey, 0, len(btcDel.FpBtcPks)) + for _, fpPk := range btcDel.FpBtcPks { + encKey, err := asig.NewEncryptionKeyFromBTCPK(fpPk) + if err != nil { + continue + // fpPkHex := bbntypes.NewBIP340PubKeyFromBTCPK(fpPk).MarshalHex() + // return nil, nil, fmt.Errorf("failed to get encryption key from finality provider public key %s: %w", fpPkHex, err) + } + + fpsEncKeys = append(fpsEncKeys, encKey) + } + + slashingPkScriptPath, stakingTxUnbondingPkScriptPath, err := generatePkScriptPathSlashAndUnbond(btcDel, params, &ce.config.BTCNetParams) if err != nil { - ce.logger.Error("failed to sign signatures or unbonding signature", zap.Error(err)) + ce.logger.Error("failed to generate pk script path for slash and unbond", zap.Error(err)) continue } - - // 7. sign covenant slash unbonding signatures - slashUnbondingSigs, err := signSlashUnbondingSignatures( - btcDel, - unbondingTx, - slashUnbondingTx, - ce.signer, - params, - &ce.config.BTCNetParams, - ) + // slashSigs, unbondingSig, err := signSlashAndUnbondSignatures( + // btcDel, + // stakingTx, + // slashingTx, + // unbondingTx, + // ce.signer, + // params, + // &ce.config.BTCNetParams, + // ) + // if err != nil { + // ce.logger.Error("failed to sign signatures or unbonding signature", zap.Error(err)) + // continue + // } + + unbondingTxSlashingScriptPath, err := pkScriptPathSlashUnbonding(btcDel, unbondingTx, params, &ce.config.BTCNetParams) if err != nil { - ce.logger.Error("failed to slash unbonding signature", zap.Error(err)) + ce.logger.Error("failed to generate pk script path for slash unbonding", zap.Error(err)) continue } + // 7. sign covenant slash unbonding signatures + // slashUnbondingSigs, err := signSlashUnbondingSignatures( + // btcDel, + // unbondingTx, + // slashUnbondingTx, + // ce.signer, + // params, + // &ce.config.BTCNetParams, + // ) + // if err != nil { + // ce.logger.Error("failed to slash unbonding signature", zap.Error(err)) + // continue + // } + + signingReq[stakingTx.TxHash()] = SigningTxsRequest{ + StakingTx: stakingTx, + SlashingTx: slashingTx, + UnbondingTx: unbondingTx, + SlashUnbondingTx: slashUnbondingTx, + StakingOutputIdx: btcDel.StakingOutputIdx, + SlashingPkScriptPath: slashingPkScriptPath, + StakingTxUnbondingPkScriptPath: stakingTxUnbondingPkScriptPath, + UnbondingTxSlashingScriptPath: unbondingTxSlashingScriptPath, + FpEncKeys: fpsEncKeys, + } + + } + + // Calls the signer to sign all + // builds the covenant signatures - // record metrics - finishSignTime := time.Now() - metricsTimeKeeper.SetPreviousSignFinish(&finishSignTime) - timedSignDelegationLag.Observe(time.Since(startSignTime).Seconds()) + // 8. sign covenant staking sigs + // record metrics + startSignTime := time.Now() + metricsTimeKeeper.SetPreviousSignStart(&startSignTime) - // 8. collect covenant sigs + // calls the signer + respSignatures, err := ce.signer.SignTransactions(SigningRequest{SigningTxsReqByStkTxHash: signingReq}) + if err != nil { + ce.recordMetricsFailedSignDelegations(len(btcDels)) + return nil, err + } + + covenantSigs := make([]*types.CovenantSigs, 0, len(btcDels)) + for stkTxHash, signatures := range respSignatures.SignaturesByStkTxHash { covenantSigs = append(covenantSigs, &types.CovenantSigs{ PublicKey: ce.pk, - StakingTxHash: stakingTx.TxHash(), - SlashingSigs: slashSigs, - UnbondingSig: unbondingSig, - SlashingUnbondingSigs: slashUnbondingSigs, + StakingTxHash: stkTxHash, + SlashingSigs: signatures.SlashSigs, + UnbondingSig: signatures.UnbondingSig, + SlashingUnbondingSigs: signatures.SlashUnbondingSigs, }) } + // record metrics + finishSignTime := time.Now() + metricsTimeKeeper.SetPreviousSignFinish(&finishSignTime) + timedSignDelegationLag.Observe(time.Since(startSignTime).Seconds()) + + // builds the covenant signs to submit + // 8. collect covenant sigs + // covenantSigs = append(covenantSigs, &types.CovenantSigs{ + // PublicKey: ce.pk, + // StakingTxHash: stakingTx.TxHash(), + // SlashingSigs: slashSigs, + // UnbondingSig: unbondingSig, + // SlashingUnbondingSigs: slashUnbondingSigs, + // }) + // 9. submit covenant sigs res, err := ce.cc.SubmitCovenantSigs(covenantSigs) if err != nil { @@ -249,6 +309,34 @@ func (ce *CovenantEmulator) AddCovenantSignatures(btcDels []*types.Delegation) ( return res, nil } +func pkScriptPathSlashUnbonding( + del *types.Delegation, + unbondingTx *wire.MsgTx, + params *types.StakingParams, + btcNet *chaincfg.Params, +) (unbondingTxSlashingScriptPath []byte, err error) { + unbondingInfo, err := btcstaking.BuildUnbondingInfo( + del.BtcPk, + del.FpBtcPks, + params.CovenantPks, + params.CovenantQuorum, + del.UnbondingTime, + btcutil.Amount(unbondingTx.TxOut[0].Value), + btcNet, + ) + if err != nil { + return nil, err + } + + unbondingTxSlashingPathInfo, err := unbondingInfo.SlashingPathSpendInfo() + if err != nil { + return nil, err + } + unbondingTxSlashingScriptPath = unbondingTxSlashingPathInfo.GetPkScriptPath() + + return unbondingTxSlashingScriptPath, nil +} + func signSlashUnbondingSignatures( del *types.Delegation, unbondingTx *wire.MsgTx, @@ -274,6 +362,7 @@ func signSlashUnbondingSignatures( if err != nil { return nil, err } + unbondingTxSlashingPathInfo := unbondingTxSlashingPath.GetPkScriptPath() slashUnbondingSigs := make([][]byte, 0, len(del.FpBtcPks)) for _, fpPk := range del.FpBtcPks { @@ -285,7 +374,7 @@ func signSlashUnbondingSignatures( slashUnbondingTx, unbondingTx, 0, // 0th output is always the unbonding script output - unbondingTxSlashingPath.GetPkScriptPath(), + unbondingTxSlashingPathInfo, encKey, ) if err != nil { @@ -297,6 +386,41 @@ func signSlashUnbondingSignatures( return slashUnbondingSigs, nil } +func generatePkScriptPathSlashAndUnbond( + del *types.Delegation, + params *types.StakingParams, + btcNet *chaincfg.Params, +) (slashingPkScriptPath, stakingTxUnbondingPkScriptPath []byte, err error) { + // sign slash signatures with every finality providers + stakingInfo, err := btcstaking.BuildStakingInfo( + del.BtcPk, + del.FpBtcPks, + params.CovenantPks, + params.CovenantQuorum, + del.GetStakingTime(), + del.TotalSat, + btcNet, + ) + if err != nil { + return nil, nil, fmt.Errorf("failed to build staking info: %w", err) + } + + slashingPathInfo, err := stakingInfo.SlashingPathSpendInfo() + if err != nil { + return nil, nil, fmt.Errorf("failed to get slashing path info: %w", err) + } + slashingPkScriptPath = slashingPathInfo.GetPkScriptPath() + + // sign unbonding sig + stakingTxUnbondingPathInfo, err := stakingInfo.UnbondingPathSpendInfo() + if err != nil { + return nil, nil, fmt.Errorf("failed to get unbonding path spend info") + } + stakingTxUnbondingPkScriptPath = stakingTxUnbondingPathInfo.GetPkScriptPath() + + return slashingPkScriptPath, stakingTxUnbondingPkScriptPath, nil +} + func signSlashAndUnbondSignatures( del *types.Delegation, stakingTx *wire.MsgTx, @@ -341,7 +465,7 @@ func signSlashAndUnbondSignatures( encKey, ) if err != nil { - return nil, nil, fmt.Errorf("failed to sign adaptor signature with finaliyt provider public key %s: %w", + return nil, nil, fmt.Errorf("failed to sign adaptor signature with finality provider public key %s: %w", fpPkHex, err) } slashSigs = append(slashSigs, slashSig.MustMarshal()) diff --git a/covenant/expected_interfaces.go b/covenant/expected_interfaces.go index 1040a0e..4b8d07b 100644 --- a/covenant/expected_interfaces.go +++ b/covenant/expected_interfaces.go @@ -3,12 +3,40 @@ package covenant import ( asig "github.com/babylonlabs-io/babylon/crypto/schnorr-adaptor-signature" "github.com/btcsuite/btcd/btcec/v2/schnorr" + "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/wire" secp "github.com/decred/dcrd/dcrec/secp256k1/v4" ) +type SigningTxsRequest struct { + StakingTx *wire.MsgTx + SlashingTx *wire.MsgTx + UnbondingTx *wire.MsgTx + SlashUnbondingTx *wire.MsgTx + StakingOutputIdx uint32 + SlashingPkScriptPath []byte + StakingTxUnbondingPkScriptPath []byte + UnbondingTxSlashingScriptPath []byte + FpEncKeys []*asig.EncryptionKey +} + +type SigningRequest struct { + SigningTxsReqByStkTxHash map[chainhash.Hash]SigningTxsRequest +} + +type SignaturesResponse struct { + SlashSigs [][]byte + UnbondingSig *schnorr.Signature + SlashUnbondingSigs [][]byte +} + +type SigningResponse struct { + SignaturesByStkTxHash map[chainhash.Hash]SignaturesResponse +} + // Signer wrapper interface to sign messages type Signer interface { + SignTransactions(req SigningRequest) (*SigningResponse, error) // PubKey returns the current secp256k1 public key PubKey() (*secp.PublicKey, error) // EncSignTxWithOneScriptSpendInputStrict is encrypted version of diff --git a/keyring/signer.go b/keyring/signer.go index 0b13a03..3f113bd 100644 --- a/keyring/signer.go +++ b/keyring/signer.go @@ -10,6 +10,7 @@ import ( asig "github.com/babylonlabs-io/babylon/crypto/schnorr-adaptor-signature" "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcec/v2/schnorr" + "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/wire" secp "github.com/decred/dcrd/dcrec/secp256k1/v4" ) @@ -118,3 +119,72 @@ func (kcs KeyringSigner) SignTxWithOneScriptSpendInputStrict( covenantPrivKey, ) } + +// SignTransactions receives a batch of transactions to sign and returns all the signatures if nothing fails. +func (kcs KeyringSigner) SignTransactions(req covenant.SigningRequest) (*covenant.SigningResponse, error) { + resp := make(map[chainhash.Hash]covenant.SignaturesResponse, len(req.SigningTxsReqByStkTxHash)) + + covenantPrivKey, err := kcs.getPrivKey() + if err != nil { + return nil, fmt.Errorf("failed to get Covenant private key: %w", err) + } + + for stakingTxHash, signingTxReq := range req.SigningTxsReqByStkTxHash { + // for each signing tx request + + slashSigs := make([][]byte, 0, len(signingTxReq.FpEncKeys)) + slashUnbondingSigs := make([][]byte, 0, len(signingTxReq.FpEncKeys)) + for _, fpEncKey := range signingTxReq.FpEncKeys { + // creates slash sigs + // TODO: split to diff func + slashSig, err := btcstaking.EncSignTxWithOneScriptSpendInputStrict( + signingTxReq.SlashingTx, + signingTxReq.StakingTx, + signingTxReq.StakingOutputIdx, + signingTxReq.SlashingPkScriptPath, + covenantPrivKey, + fpEncKey, + ) + if err != nil { + return nil, fmt.Errorf("failed to sign adaptor slash signature with finality provider public key %s: %w", fpEncKey.ToBytes(), err) + } + slashSigs = append(slashSigs, slashSig.MustMarshal()) + + // TODO: split to diff func + // creates slash unbonding sig + slashUnbondingSig, err := btcstaking.EncSignTxWithOneScriptSpendInputStrict( + signingTxReq.SlashUnbondingTx, + signingTxReq.UnbondingTx, + 0, // 0th output is always the unbonding script output + signingTxReq.UnbondingTxSlashingScriptPath, + covenantPrivKey, + fpEncKey, + ) + if err != nil { + return nil, fmt.Errorf("failed to sign adaptor slash unbonding signature with finality provider public key %s: %w", fpEncKey.ToBytes(), err) + } + slashUnbondingSigs = append(slashUnbondingSigs, slashUnbondingSig.MustMarshal()) + } + + unbondingSig, err := btcstaking.SignTxWithOneScriptSpendInputStrict( + signingTxReq.UnbondingTx, + signingTxReq.StakingTx, + signingTxReq.StakingOutputIdx, + signingTxReq.StakingTxUnbondingPkScriptPath, + covenantPrivKey, + ) + if err != nil { + return nil, fmt.Errorf("failed to sign unbonding tx: %w", err) + } + + resp[stakingTxHash] = covenant.SignaturesResponse{ + SlashSigs: slashSigs, + UnbondingSig: unbondingSig, + SlashUnbondingSigs: slashUnbondingSigs, + } + } + + return &covenant.SigningResponse{ + SignaturesByStkTxHash: resp, + }, nil +} From 32f2b7a623d6229ad7c61367dbf02979d8288c62 Mon Sep 17 00:00:00 2001 From: RafilxTenfen Date: Thu, 10 Oct 2024 16:56:41 -0300 Subject: [PATCH 07/17] chore: removed unused funcs, simplified sort --- covenant/covenant.go | 290 ++++++++++---------------------- covenant/expected_interfaces.go | 58 ++----- keyring/signer.go | 58 +------ 3 files changed, 111 insertions(+), 295 deletions(-) diff --git a/covenant/covenant.go b/covenant/covenant.go index 0b4a6de..ebe5dd8 100644 --- a/covenant/covenant.go +++ b/covenant/covenant.go @@ -3,6 +3,8 @@ package covenant import ( "encoding/hex" "fmt" + "sort" + "strings" "sync" "time" @@ -17,6 +19,7 @@ import ( "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/wire" + secp "github.com/decred/dcrd/dcrec/secp256k1/v4" "go.uber.org/zap" "github.com/babylonlabs-io/covenant-emulator/clientcontroller" @@ -86,7 +89,7 @@ func (ce *CovenantEmulator) AddCovenantSignatures(btcDels []*types.Delegation) ( return nil, fmt.Errorf("no delegations") } - signingReq := make(map[chainhash.Hash]SigningTxsRequest) + signingReq := make(map[chainhash.Hash]SigningTxsRequest, len(btcDels)) for _, btcDel := range btcDels { // 0. nil checks if btcDel == nil { @@ -187,129 +190,107 @@ func (ce *CovenantEmulator) AddCovenantSignatures(btcDels []*types.Delegation) ( continue } - // Generate signing request data. - - fpsEncKeys := make([]*asig.EncryptionKey, 0, len(btcDel.FpBtcPks)) - for _, fpPk := range btcDel.FpBtcPks { - encKey, err := asig.NewEncryptionKeyFromBTCPK(fpPk) - if err != nil { - continue - // fpPkHex := bbntypes.NewBIP340PubKeyFromBTCPK(fpPk).MarshalHex() - // return nil, nil, fmt.Errorf("failed to get encryption key from finality provider public key %s: %w", fpPkHex, err) - } - - fpsEncKeys = append(fpsEncKeys, encKey) - } - - slashingPkScriptPath, stakingTxUnbondingPkScriptPath, err := generatePkScriptPathSlashAndUnbond(btcDel, params, &ce.config.BTCNetParams) + // 8. Generate Signing Request + // Finality providers encription keys + // pk script paths for Slash, unbond and unbonding slashing + fpsEncKeys, err := fpEncKeysFromDel(btcDel) if err != nil { - ce.logger.Error("failed to generate pk script path for slash and unbond", zap.Error(err)) + ce.logger.Error("failed to encript the finality provider keys of the btc delegation", zap.String("staker_pk", stakerPkHex), zap.Error(err)) continue } - // slashSigs, unbondingSig, err := signSlashAndUnbondSignatures( - // btcDel, - // stakingTx, - // slashingTx, - // unbondingTx, - // ce.signer, - // params, - // &ce.config.BTCNetParams, - // ) - // if err != nil { - // ce.logger.Error("failed to sign signatures or unbonding signature", zap.Error(err)) - // continue - // } - - unbondingTxSlashingScriptPath, err := pkScriptPathSlashUnbonding(btcDel, unbondingTx, params, &ce.config.BTCNetParams) + + slashingPkScriptPath, stakingTxUnbondingPkScriptPath, unbondingTxSlashingPkScriptPath, err := pkScriptPaths(btcDel, params, &ce.config.BTCNetParams, unbondingTx) if err != nil { - ce.logger.Error("failed to generate pk script path for slash unbonding", zap.Error(err)) + ce.logger.Error("failed to generate pk script path", zap.Error(err)) continue } - // 7. sign covenant slash unbonding signatures - // slashUnbondingSigs, err := signSlashUnbondingSignatures( - // btcDel, - // unbondingTx, - // slashUnbondingTx, - // ce.signer, - // params, - // &ce.config.BTCNetParams, - // ) - // if err != nil { - // ce.logger.Error("failed to slash unbonding signature", zap.Error(err)) - // continue - // } signingReq[stakingTx.TxHash()] = SigningTxsRequest{ - StakingTx: stakingTx, - SlashingTx: slashingTx, - UnbondingTx: unbondingTx, - SlashUnbondingTx: slashUnbondingTx, - StakingOutputIdx: btcDel.StakingOutputIdx, - SlashingPkScriptPath: slashingPkScriptPath, - StakingTxUnbondingPkScriptPath: stakingTxUnbondingPkScriptPath, - UnbondingTxSlashingScriptPath: unbondingTxSlashingScriptPath, - FpEncKeys: fpsEncKeys, + StakingTx: stakingTx, + SlashingTx: slashingTx, + UnbondingTx: unbondingTx, + SlashUnbondingTx: slashUnbondingTx, + StakingOutputIdx: btcDel.StakingOutputIdx, + SlashingPkScriptPath: slashingPkScriptPath, + StakingTxUnbondingPkScriptPath: stakingTxUnbondingPkScriptPath, + UnbondingTxSlashingPkScriptPath: unbondingTxSlashingPkScriptPath, + FpEncKeys: fpsEncKeys, } - } - // Calls the signer to sign all - // builds the covenant signatures + respSigs, err := ce.SignTransactions(signingReq) + if err != nil { + return nil, err + } - // 8. sign covenant staking sigs - // record metrics - startSignTime := time.Now() - metricsTimeKeeper.SetPreviousSignStart(&startSignTime) + covenantSigs := BuildCovenantSigs(ce.pk, *respSigs) - // calls the signer - respSignatures, err := ce.signer.SignTransactions(SigningRequest{SigningTxsReqByStkTxHash: signingReq}) + // 9. submit covenant sigs + res, err := ce.cc.SubmitCovenantSigs(covenantSigs) if err != nil { - ce.recordMetricsFailedSignDelegations(len(btcDels)) + ce.recordMetricsFailedSignDelegations(len(covenantSigs)) return nil, err } - covenantSigs := make([]*types.CovenantSigs, 0, len(btcDels)) - for stkTxHash, signatures := range respSignatures.SignaturesByStkTxHash { - covenantSigs = append(covenantSigs, &types.CovenantSigs{ - PublicKey: ce.pk, + // record metrics + submittedTime := time.Now() + metricsTimeKeeper.SetPreviousSubmission(&submittedTime) + ce.recordMetricsTotalSignDelegationsSubmitted(len(covenantSigs)) + + return res, nil +} + +// BuildCovenantSigs creates the covenant signatures from the signature response +func BuildCovenantSigs(pk *secp.PublicKey, resp SigningResponse) []types.CovenantSigs { + covenantSigs := make([]types.CovenantSigs, 0, len(resp.SignaturesByStkTxHash)) + for stkTxHash, signatures := range resp.SignaturesByStkTxHash { + covenantSigs = append(covenantSigs, types.CovenantSigs{ + PublicKey: *pk, StakingTxHash: stkTxHash, SlashingSigs: signatures.SlashSigs, - UnbondingSig: signatures.UnbondingSig, + UnbondingSig: *signatures.UnbondingSig, SlashingUnbondingSigs: signatures.SlashUnbondingSigs, }) } + return SortCovenantSigs(covenantSigs) +} +// SignTransactions calls the signer and record metrics about signing +func (ce *CovenantEmulator) SignTransactions(signingReq map[chainhash.Hash]SigningTxsRequest) (*SigningResponse, error) { // record metrics - finishSignTime := time.Now() - metricsTimeKeeper.SetPreviousSignFinish(&finishSignTime) - timedSignDelegationLag.Observe(time.Since(startSignTime).Seconds()) - - // builds the covenant signs to submit - // 8. collect covenant sigs - // covenantSigs = append(covenantSigs, &types.CovenantSigs{ - // PublicKey: ce.pk, - // StakingTxHash: stakingTx.TxHash(), - // SlashingSigs: slashSigs, - // UnbondingSig: unbondingSig, - // SlashingUnbondingSigs: slashUnbondingSigs, - // }) + startSignTime := time.Now() + metricsTimeKeeper.SetPreviousSignStart(&startSignTime) - // 9. submit covenant sigs - res, err := ce.cc.SubmitCovenantSigs(covenantSigs) + // 8. sign covenant transactions + respSignatures, err := ce.signer.SignTransactions(SigningRequest{SigningTxsReqByStkTxHash: signingReq}) if err != nil { - ce.recordMetricsFailedSignDelegations(len(covenantSigs)) + ce.recordMetricsFailedSignDelegations(len(signingReq)) return nil, err } // record metrics - submittedTime := time.Now() - metricsTimeKeeper.SetPreviousSubmission(&submittedTime) - ce.recordMetricsTotalSignDelegationsSubmitted(len(covenantSigs)) + finishSignTime := time.Now() + metricsTimeKeeper.SetPreviousSignFinish(&finishSignTime) + timedSignDelegationLag.Observe(time.Since(startSignTime).Seconds()) - return res, nil + return respSignatures, nil +} + +func fpEncKeysFromDel(btcDel *types.Delegation) ([]*asig.EncryptionKey, error) { + fpsEncKeys := make([]*asig.EncryptionKey, 0, len(btcDel.FpBtcPks)) + for _, fpPk := range btcDel.FpBtcPks { + encKey, err := asig.NewEncryptionKeyFromBTCPK(fpPk) + if err != nil { + fpPkHex := bbntypes.NewBIP340PubKeyFromBTCPK(fpPk).MarshalHex() + return nil, fmt.Errorf("failed to get encryption key from finality provider public key %s: %w", fpPkHex, err) + } + fpsEncKeys = append(fpsEncKeys, encKey) + } + + return fpsEncKeys, nil } -func pkScriptPathSlashUnbonding( +func pkScriptPathUnbondingSlash( del *types.Delegation, unbondingTx *wire.MsgTx, params *types.StakingParams, @@ -337,56 +318,26 @@ func pkScriptPathSlashUnbonding( return unbondingTxSlashingScriptPath, nil } -func signSlashUnbondingSignatures( +func pkScriptPaths( del *types.Delegation, - unbondingTx *wire.MsgTx, - slashUnbondingTx *wire.MsgTx, - signer Signer, params *types.StakingParams, btcNet *chaincfg.Params, -) ([][]byte, error) { - unbondingInfo, err := btcstaking.BuildUnbondingInfo( - del.BtcPk, - del.FpBtcPks, - params.CovenantPks, - params.CovenantQuorum, - del.UnbondingTime, - btcutil.Amount(unbondingTx.TxOut[0].Value), - btcNet, - ) + unbondingTx *wire.MsgTx, +) (slash, unbond, unbondSlash []byte, err error) { + slash, unbond, err = pkScriptPathSlashAndUnbond(del, params, btcNet) if err != nil { - return nil, err + return nil, nil, nil, err } - unbondingTxSlashingPath, err := unbondingInfo.SlashingPathSpendInfo() + unbondSlash, err = pkScriptPathUnbondingSlash(del, unbondingTx, params, btcNet) if err != nil { - return nil, err - } - unbondingTxSlashingPathInfo := unbondingTxSlashingPath.GetPkScriptPath() - - slashUnbondingSigs := make([][]byte, 0, len(del.FpBtcPks)) - for _, fpPk := range del.FpBtcPks { - encKey, err := asig.NewEncryptionKeyFromBTCPK(fpPk) - if err != nil { - return nil, err - } - slashUnbondingSig, err := signer.EncSignTxWithOneScriptSpendInputStrict( - slashUnbondingTx, - unbondingTx, - 0, // 0th output is always the unbonding script output - unbondingTxSlashingPathInfo, - encKey, - ) - if err != nil { - return nil, err - } - slashUnbondingSigs = append(slashUnbondingSigs, slashUnbondingSig.MustMarshal()) + return nil, nil, nil, err } - return slashUnbondingSigs, nil + return slash, unbond, unbondSlash, nil } -func generatePkScriptPathSlashAndUnbond( +func pkScriptPathSlashAndUnbond( del *types.Delegation, params *types.StakingParams, btcNet *chaincfg.Params, @@ -421,74 +372,6 @@ func generatePkScriptPathSlashAndUnbond( return slashingPkScriptPath, stakingTxUnbondingPkScriptPath, nil } -func signSlashAndUnbondSignatures( - del *types.Delegation, - stakingTx *wire.MsgTx, - slashingTx *wire.MsgTx, - unbondingTx *wire.MsgTx, - signer Signer, - params *types.StakingParams, - btcNet *chaincfg.Params, -) ([][]byte, *schnorr.Signature, error) { - // sign slash signatures with every finality providers - stakingInfo, err := btcstaking.BuildStakingInfo( - del.BtcPk, - del.FpBtcPks, - params.CovenantPks, - params.CovenantQuorum, - del.GetStakingTime(), - del.TotalSat, - btcNet, - ) - if err != nil { - return nil, nil, fmt.Errorf("failed to build staking info: %w", err) - } - - slashingPathInfo, err := stakingInfo.SlashingPathSpendInfo() - if err != nil { - return nil, nil, fmt.Errorf("failed to get slashing path info: %w", err) - } - - slashSigs := make([][]byte, 0, len(del.FpBtcPks)) - for _, fpPk := range del.FpBtcPks { - fpPkHex := bbntypes.NewBIP340PubKeyFromBTCPK(fpPk).MarshalHex() - encKey, err := asig.NewEncryptionKeyFromBTCPK(fpPk) - if err != nil { - return nil, nil, fmt.Errorf("failed to get encryption key from finality provider public key %s: %w", - fpPkHex, err) - } - slashSig, err := signer.EncSignTxWithOneScriptSpendInputStrict( - slashingTx, - stakingTx, - del.StakingOutputIdx, - slashingPathInfo.GetPkScriptPath(), - encKey, - ) - if err != nil { - return nil, nil, fmt.Errorf("failed to sign adaptor signature with finality provider public key %s: %w", - fpPkHex, err) - } - slashSigs = append(slashSigs, slashSig.MustMarshal()) - } - - // sign unbonding sig - stakingTxUnbondingPathInfo, err := stakingInfo.UnbondingPathSpendInfo() - if err != nil { - return nil, nil, fmt.Errorf("failed to get unbonding path spend info") - } - unbondingSig, err := signer.SignTxWithOneScriptSpendInputStrict( - unbondingTx, - stakingTx, - del.StakingOutputIdx, - stakingTxUnbondingPathInfo.GetPkScriptPath(), - ) - if err != nil { - return nil, nil, fmt.Errorf("failed to sign unbonding tx: %w", err) - } - - return slashSigs, unbondingSig, nil -} - func decodeDelegationTransactions(del *types.Delegation, params *types.StakingParams, btcNet *chaincfg.Params) (*wire.MsgTx, *wire.MsgTx, error) { // 1. decode staking tx and slashing tx stakingMsgTx, _, err := bbntypes.NewBTCTxFromHex(del.StakingTxHex) @@ -723,3 +606,14 @@ func (ce *CovenantEmulator) Stop() error { }) return stopErr } + +// SortCovenantSigs helper function to sort all covenant signatures by the staking tx hash +func SortCovenantSigs(covSigs []types.CovenantSigs) []types.CovenantSigs { + sorted := make([]types.CovenantSigs, len(covSigs)) + copy(sorted, covSigs) + sort.SliceStable(sorted, func(i, j int) bool { + return strings.Compare(sorted[i].StakingTxHash.String(), sorted[j].StakingTxHash.String()) == 1 + }) + + return sorted +} diff --git a/covenant/expected_interfaces.go b/covenant/expected_interfaces.go index 4b8d07b..7701189 100644 --- a/covenant/expected_interfaces.go +++ b/covenant/expected_interfaces.go @@ -8,16 +8,25 @@ import ( secp "github.com/decred/dcrd/dcrec/secp256k1/v4" ) +// Signer wrapper interface to sign messages +type Signer interface { + // SignTransactions signs all the transactions from the request + // and returns all the signatures for Slash, Unbond and Unbonding Slash. + SignTransactions(req SigningRequest) (*SigningResponse, error) + // PubKey returns the current secp256k1 public key + PubKey() (*secp.PublicKey, error) +} + type SigningTxsRequest struct { - StakingTx *wire.MsgTx - SlashingTx *wire.MsgTx - UnbondingTx *wire.MsgTx - SlashUnbondingTx *wire.MsgTx - StakingOutputIdx uint32 - SlashingPkScriptPath []byte - StakingTxUnbondingPkScriptPath []byte - UnbondingTxSlashingScriptPath []byte - FpEncKeys []*asig.EncryptionKey + StakingTx *wire.MsgTx + SlashingTx *wire.MsgTx + UnbondingTx *wire.MsgTx + SlashUnbondingTx *wire.MsgTx + StakingOutputIdx uint32 + SlashingPkScriptPath []byte + StakingTxUnbondingPkScriptPath []byte + UnbondingTxSlashingPkScriptPath []byte + FpEncKeys []*asig.EncryptionKey } type SigningRequest struct { @@ -33,34 +42,3 @@ type SignaturesResponse struct { type SigningResponse struct { SignaturesByStkTxHash map[chainhash.Hash]SignaturesResponse } - -// Signer wrapper interface to sign messages -type Signer interface { - SignTransactions(req SigningRequest) (*SigningResponse, error) - // PubKey returns the current secp256k1 public key - PubKey() (*secp.PublicKey, error) - // EncSignTxWithOneScriptSpendInputStrict is encrypted version of - // SignTxWithOneScriptSpendInputStrict with the output to be encrypted - // by an encryption key (adaptor signature) - EncSignTxWithOneScriptSpendInputStrict( - txToSign *wire.MsgTx, - fundingTx *wire.MsgTx, - fundingOutputIdx uint32, - signedScriptPath []byte, - encKey *asig.EncryptionKey, - ) (*asig.AdaptorSignature, error) - // SignTxWithOneScriptSpendInputStrict signs transaction with one input coming - // from script spend output with provided script. - // It checks: - // - txToSign is not nil - // - txToSign has exactly one input - // - fundingTx is not nil - // - fundingTx has one output committing to the provided script - // - txToSign input is pointing to the correct output in fundingTx - SignTxWithOneScriptSpendInputStrict( - txToSign *wire.MsgTx, - fundingTx *wire.MsgTx, - fundingOutputIdx uint32, - signedScriptPath []byte, - ) (*schnorr.Signature, error) -} diff --git a/keyring/signer.go b/keyring/signer.go index 3f113bd..efe04ef 100644 --- a/keyring/signer.go +++ b/keyring/signer.go @@ -7,11 +7,8 @@ import ( "github.com/babylonlabs-io/babylon/btcstaking" "github.com/babylonlabs-io/covenant-emulator/covenant" - asig "github.com/babylonlabs-io/babylon/crypto/schnorr-adaptor-signature" "github.com/btcsuite/btcd/btcec/v2" - "github.com/btcsuite/btcd/btcec/v2/schnorr" "github.com/btcsuite/btcd/chaincfg/chainhash" - "github.com/btcsuite/btcd/wire" secp "github.com/decred/dcrd/dcrec/secp256k1/v4" ) @@ -67,59 +64,6 @@ func (kcs KeyringSigner) getPrivKey() (*btcec.PrivateKey, error) { return privKey, nil } -// EncSignTxWithOneScriptSpendInputStrict is encrypted version of -// SignTxWithOneScriptSpendInputStrict with the output to be encrypted -// by an encryption key (adaptor signature) -func (kcs KeyringSigner) EncSignTxWithOneScriptSpendInputStrict( - txToSign *wire.MsgTx, - fundingTx *wire.MsgTx, - fundingOutputIdx uint32, - signedScriptPath []byte, - encKey *asig.EncryptionKey, -) (*asig.AdaptorSignature, error) { - covenantPrivKey, err := kcs.getPrivKey() - if err != nil { - return nil, fmt.Errorf("failed to get Covenant private key: %w", err) - } - - return btcstaking.EncSignTxWithOneScriptSpendInputStrict( - txToSign, - fundingTx, - fundingOutputIdx, - signedScriptPath, - covenantPrivKey, - encKey, - ) -} - -// SignTxWithOneScriptSpendInputStrict signs transaction with one input coming -// from script spend output with provided script. -// It checks: -// - txToSign is not nil -// - txToSign has exactly one input -// - fundingTx is not nil -// - fundingTx has one output committing to the provided script -// - txToSign input is pointing to the correct output in fundingTx -func (kcs KeyringSigner) SignTxWithOneScriptSpendInputStrict( - txToSign *wire.MsgTx, - fundingTx *wire.MsgTx, - fundingOutputIdx uint32, - signedScriptPath []byte, -) (*schnorr.Signature, error) { - covenantPrivKey, err := kcs.getPrivKey() - if err != nil { - return nil, fmt.Errorf("failed to get Covenant private key: %w", err) - } - - return btcstaking.SignTxWithOneScriptSpendInputStrict( - txToSign, - fundingTx, - fundingOutputIdx, - signedScriptPath, - covenantPrivKey, - ) -} - // SignTransactions receives a batch of transactions to sign and returns all the signatures if nothing fails. func (kcs KeyringSigner) SignTransactions(req covenant.SigningRequest) (*covenant.SigningResponse, error) { resp := make(map[chainhash.Hash]covenant.SignaturesResponse, len(req.SigningTxsReqByStkTxHash)) @@ -156,7 +100,7 @@ func (kcs KeyringSigner) SignTransactions(req covenant.SigningRequest) (*covenan signingTxReq.SlashUnbondingTx, signingTxReq.UnbondingTx, 0, // 0th output is always the unbonding script output - signingTxReq.UnbondingTxSlashingScriptPath, + signingTxReq.UnbondingTxSlashingPkScriptPath, covenantPrivKey, fpEncKey, ) From 9633554b3a68706d3c20686743be0d43ef1a8f6f Mon Sep 17 00:00:00 2001 From: RafilxTenfen Date: Thu, 10 Oct 2024 16:57:00 -0300 Subject: [PATCH 08/17] fix: unit test sorting of covenant sigs to match expected --- clientcontroller/babylon.go | 6 +++--- clientcontroller/interface.go | 2 +- covenant/covenant_test.go | 14 +++++++++----- testutil/mocks/babylon.go | 2 +- types/sigs.go | 4 ++-- 5 files changed, 16 insertions(+), 12 deletions(-) diff --git a/clientcontroller/babylon.go b/clientcontroller/babylon.go index 121c1f4..97f4432 100644 --- a/clientcontroller/babylon.go +++ b/clientcontroller/babylon.go @@ -158,13 +158,13 @@ func (bc *BabylonController) reliablySendMsgs(msgs []sdk.Msg) (*provider.Relayer // SubmitCovenantSigs submits the Covenant signature via a MsgAddCovenantSig to Babylon if the daemon runs in Covenant mode // it returns tx hash and error -func (bc *BabylonController) SubmitCovenantSigs(covSigs []*types.CovenantSigs) (*types.TxResponse, error) { +func (bc *BabylonController) SubmitCovenantSigs(covSigs []types.CovenantSigs) (*types.TxResponse, error) { msgs := make([]sdk.Msg, 0, len(covSigs)) for _, covSig := range covSigs { - bip340UnbondingSig := bbntypes.NewBIP340SignatureFromBTCSig(covSig.UnbondingSig) + bip340UnbondingSig := bbntypes.NewBIP340SignatureFromBTCSig(&covSig.UnbondingSig) msgs = append(msgs, &btcstakingtypes.MsgAddCovenantSigs{ Signer: bc.mustGetTxSigner(), - Pk: bbntypes.NewBIP340PubKeyFromBTCPK(covSig.PublicKey), + Pk: bbntypes.NewBIP340PubKeyFromBTCPK(&covSig.PublicKey), StakingTxHash: covSig.StakingTxHash.String(), SlashingTxSigs: covSig.SlashingSigs, UnbondingTxSig: bip340UnbondingSig, diff --git a/clientcontroller/interface.go b/clientcontroller/interface.go index 7d15314..dfcd8c5 100644 --- a/clientcontroller/interface.go +++ b/clientcontroller/interface.go @@ -18,7 +18,7 @@ type ClientController interface { // SubmitCovenantSigs submits Covenant signatures to the consumer chain, each corresponding to // a finality provider that the delegation is (re-)staked to // it returns tx hash and error - SubmitCovenantSigs(covSigMsgs []*types.CovenantSigs) (*types.TxResponse, error) + SubmitCovenantSigs(covSigMsgs []types.CovenantSigs) (*types.TxResponse, error) // QueryPendingDelegations queries BTC delegations that are in status of pending QueryPendingDelegations(limit uint64) ([]*types.Delegation, error) diff --git a/covenant/covenant_test.go b/covenant/covenant_test.go index 795f4b5..78f57da 100644 --- a/covenant/covenant_test.go +++ b/covenant/covenant_test.go @@ -33,6 +33,7 @@ var net = &chaincfg.SimNetParams func FuzzAddCovenantSig(f *testing.F) { testutil.AddRandomSeedsToFuzzer(f, 10) f.Fuzz(func(t *testing.T, seed int64) { + t.Log("Seed", seed) r := rand.New(rand.NewSource(seed)) params := testutil.GenRandomParams(r, t) @@ -40,6 +41,8 @@ func FuzzAddCovenantSig(f *testing.F) { // create a Covenant key pair in the keyring covenantConfig := covcfg.DefaultConfig() + covenantConfig.BabylonConfig.KeyDirectory = t.TempDir() + covKeyPair, err := keyring.CreateCovenantKey( covenantConfig.BabylonConfig.KeyDirectory, covenantConfig.BabylonConfig.ChainID, @@ -58,7 +61,7 @@ func FuzzAddCovenantSig(f *testing.F) { require.NoError(t, err) numDels := datagen.RandomInt(r, 3) + 1 - covSigsSet := make([]*types.CovenantSigs, 0, numDels) + covSigsSet := make([]types.CovenantSigs, 0, numDels) btcDels := make([]*types.Delegation, 0, numDels) for i := 0; uint64(i) < numDels; i++ { // generate BTC delegation @@ -176,11 +179,11 @@ func FuzzAddCovenantSig(f *testing.F) { require.NoError(t, err) unbondingCovSlashingSigs = append(unbondingCovSlashingSigs, covenantSig.MustMarshal()) } - covSigsSet = append(covSigsSet, &types.CovenantSigs{ - PublicKey: covKeyPair.PublicKey, + covSigsSet = append(covSigsSet, types.CovenantSigs{ + PublicKey: *covKeyPair.PublicKey, StakingTxHash: testInfo.StakingTx.TxHash(), SlashingSigs: covSigs, - UnbondingSig: unbondingCovSig, + UnbondingSig: *unbondingCovSig, SlashingUnbondingSigs: unbondingCovSlashingSigs, }) } @@ -191,9 +194,10 @@ func FuzzAddCovenantSig(f *testing.F) { } btcDels = append(btcDels, invalidDelegation) + sortedCovSigs := covenant.SortCovenantSigs(covSigsSet) // check the sigs are expected expectedTxHash := testutil.GenRandomHexStr(r, 32) - mockClientController.EXPECT().SubmitCovenantSigs(covSigsSet). + mockClientController.EXPECT().SubmitCovenantSigs(sortedCovSigs). Return(&types.TxResponse{TxHash: expectedTxHash}, nil).AnyTimes() res, err := ce.AddCovenantSignatures(btcDels) require.NoError(t, err) diff --git a/testutil/mocks/babylon.go b/testutil/mocks/babylon.go index 6fd3173..879169f 100644 --- a/testutil/mocks/babylon.go +++ b/testutil/mocks/babylon.go @@ -79,7 +79,7 @@ func (mr *MockClientControllerMockRecorder) QueryStakingParamsByVersion(version } // SubmitCovenantSigs mocks base method. -func (m *MockClientController) SubmitCovenantSigs(covSigMsgs []*types.CovenantSigs) (*types.TxResponse, error) { +func (m *MockClientController) SubmitCovenantSigs(covSigMsgs []types.CovenantSigs) (*types.TxResponse, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "SubmitCovenantSigs", covSigMsgs) ret0, _ := ret[0].(*types.TxResponse) diff --git a/types/sigs.go b/types/sigs.go index c324ca7..cb39247 100644 --- a/types/sigs.go +++ b/types/sigs.go @@ -7,9 +7,9 @@ import ( ) type CovenantSigs struct { - PublicKey *btcec.PublicKey + PublicKey btcec.PublicKey StakingTxHash chainhash.Hash SlashingSigs [][]byte - UnbondingSig *schnorr.Signature + UnbondingSig schnorr.Signature SlashingUnbondingSigs [][]byte } From 05a74c89c84e7ca960d028ed47ea22c49c51e43c Mon Sep 17 00:00:00 2001 From: RafilxTenfen Date: Thu, 10 Oct 2024 16:57:57 -0300 Subject: [PATCH 09/17] chore: rollback to use pub key with pointer --- clientcontroller/babylon.go | 2 +- covenant/covenant.go | 2 +- covenant/covenant_test.go | 2 +- types/sigs.go | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/clientcontroller/babylon.go b/clientcontroller/babylon.go index 97f4432..ec86156 100644 --- a/clientcontroller/babylon.go +++ b/clientcontroller/babylon.go @@ -164,7 +164,7 @@ func (bc *BabylonController) SubmitCovenantSigs(covSigs []types.CovenantSigs) (* bip340UnbondingSig := bbntypes.NewBIP340SignatureFromBTCSig(&covSig.UnbondingSig) msgs = append(msgs, &btcstakingtypes.MsgAddCovenantSigs{ Signer: bc.mustGetTxSigner(), - Pk: bbntypes.NewBIP340PubKeyFromBTCPK(&covSig.PublicKey), + Pk: bbntypes.NewBIP340PubKeyFromBTCPK(covSig.PublicKey), StakingTxHash: covSig.StakingTxHash.String(), SlashingTxSigs: covSig.SlashingSigs, UnbondingTxSig: bip340UnbondingSig, diff --git a/covenant/covenant.go b/covenant/covenant.go index ebe5dd8..dd69780 100644 --- a/covenant/covenant.go +++ b/covenant/covenant.go @@ -245,7 +245,7 @@ func BuildCovenantSigs(pk *secp.PublicKey, resp SigningResponse) []types.Covenan covenantSigs := make([]types.CovenantSigs, 0, len(resp.SignaturesByStkTxHash)) for stkTxHash, signatures := range resp.SignaturesByStkTxHash { covenantSigs = append(covenantSigs, types.CovenantSigs{ - PublicKey: *pk, + PublicKey: pk, StakingTxHash: stkTxHash, SlashingSigs: signatures.SlashSigs, UnbondingSig: *signatures.UnbondingSig, diff --git a/covenant/covenant_test.go b/covenant/covenant_test.go index 78f57da..75f3bc0 100644 --- a/covenant/covenant_test.go +++ b/covenant/covenant_test.go @@ -180,7 +180,7 @@ func FuzzAddCovenantSig(f *testing.F) { unbondingCovSlashingSigs = append(unbondingCovSlashingSigs, covenantSig.MustMarshal()) } covSigsSet = append(covSigsSet, types.CovenantSigs{ - PublicKey: *covKeyPair.PublicKey, + PublicKey: covKeyPair.PublicKey, StakingTxHash: testInfo.StakingTx.TxHash(), SlashingSigs: covSigs, UnbondingSig: *unbondingCovSig, diff --git a/types/sigs.go b/types/sigs.go index cb39247..228dd60 100644 --- a/types/sigs.go +++ b/types/sigs.go @@ -7,7 +7,7 @@ import ( ) type CovenantSigs struct { - PublicKey btcec.PublicKey + PublicKey *btcec.PublicKey StakingTxHash chainhash.Hash SlashingSigs [][]byte UnbondingSig schnorr.Signature From edeaf992c82da9ae67f0eaab6b9e74b4d1958dd4 Mon Sep 17 00:00:00 2001 From: RafilxTenfen Date: Thu, 10 Oct 2024 16:58:35 -0300 Subject: [PATCH 10/17] chore: rollback to schnorr sig with pointer --- clientcontroller/babylon.go | 2 +- covenant/covenant.go | 2 +- covenant/covenant_test.go | 2 +- types/sigs.go | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/clientcontroller/babylon.go b/clientcontroller/babylon.go index ec86156..cbdd848 100644 --- a/clientcontroller/babylon.go +++ b/clientcontroller/babylon.go @@ -161,7 +161,7 @@ func (bc *BabylonController) reliablySendMsgs(msgs []sdk.Msg) (*provider.Relayer func (bc *BabylonController) SubmitCovenantSigs(covSigs []types.CovenantSigs) (*types.TxResponse, error) { msgs := make([]sdk.Msg, 0, len(covSigs)) for _, covSig := range covSigs { - bip340UnbondingSig := bbntypes.NewBIP340SignatureFromBTCSig(&covSig.UnbondingSig) + bip340UnbondingSig := bbntypes.NewBIP340SignatureFromBTCSig(covSig.UnbondingSig) msgs = append(msgs, &btcstakingtypes.MsgAddCovenantSigs{ Signer: bc.mustGetTxSigner(), Pk: bbntypes.NewBIP340PubKeyFromBTCPK(covSig.PublicKey), diff --git a/covenant/covenant.go b/covenant/covenant.go index dd69780..c0eb791 100644 --- a/covenant/covenant.go +++ b/covenant/covenant.go @@ -248,7 +248,7 @@ func BuildCovenantSigs(pk *secp.PublicKey, resp SigningResponse) []types.Covenan PublicKey: pk, StakingTxHash: stkTxHash, SlashingSigs: signatures.SlashSigs, - UnbondingSig: *signatures.UnbondingSig, + UnbondingSig: signatures.UnbondingSig, SlashingUnbondingSigs: signatures.SlashUnbondingSigs, }) } diff --git a/covenant/covenant_test.go b/covenant/covenant_test.go index 75f3bc0..46db9a5 100644 --- a/covenant/covenant_test.go +++ b/covenant/covenant_test.go @@ -183,7 +183,7 @@ func FuzzAddCovenantSig(f *testing.F) { PublicKey: covKeyPair.PublicKey, StakingTxHash: testInfo.StakingTx.TxHash(), SlashingSigs: covSigs, - UnbondingSig: *unbondingCovSig, + UnbondingSig: unbondingCovSig, SlashingUnbondingSigs: unbondingCovSlashingSigs, }) } diff --git a/types/sigs.go b/types/sigs.go index 228dd60..c324ca7 100644 --- a/types/sigs.go +++ b/types/sigs.go @@ -10,6 +10,6 @@ type CovenantSigs struct { PublicKey *btcec.PublicKey StakingTxHash chainhash.Hash SlashingSigs [][]byte - UnbondingSig schnorr.Signature + UnbondingSig *schnorr.Signature SlashingUnbondingSigs [][]byte } From 562052f6ef472f8c03aee7351c1db9f74a5fab01 Mon Sep 17 00:00:00 2001 From: RafilxTenfen Date: Thu, 10 Oct 2024 17:00:38 -0300 Subject: [PATCH 11/17] chore: rollback to use covenant sigs as pointer --- clientcontroller/babylon.go | 2 +- clientcontroller/interface.go | 2 +- covenant/covenant.go | 10 +++++----- covenant/covenant_test.go | 4 ++-- testutil/mocks/babylon.go | 2 +- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/clientcontroller/babylon.go b/clientcontroller/babylon.go index cbdd848..121c1f4 100644 --- a/clientcontroller/babylon.go +++ b/clientcontroller/babylon.go @@ -158,7 +158,7 @@ func (bc *BabylonController) reliablySendMsgs(msgs []sdk.Msg) (*provider.Relayer // SubmitCovenantSigs submits the Covenant signature via a MsgAddCovenantSig to Babylon if the daemon runs in Covenant mode // it returns tx hash and error -func (bc *BabylonController) SubmitCovenantSigs(covSigs []types.CovenantSigs) (*types.TxResponse, error) { +func (bc *BabylonController) SubmitCovenantSigs(covSigs []*types.CovenantSigs) (*types.TxResponse, error) { msgs := make([]sdk.Msg, 0, len(covSigs)) for _, covSig := range covSigs { bip340UnbondingSig := bbntypes.NewBIP340SignatureFromBTCSig(covSig.UnbondingSig) diff --git a/clientcontroller/interface.go b/clientcontroller/interface.go index dfcd8c5..7d15314 100644 --- a/clientcontroller/interface.go +++ b/clientcontroller/interface.go @@ -18,7 +18,7 @@ type ClientController interface { // SubmitCovenantSigs submits Covenant signatures to the consumer chain, each corresponding to // a finality provider that the delegation is (re-)staked to // it returns tx hash and error - SubmitCovenantSigs(covSigMsgs []types.CovenantSigs) (*types.TxResponse, error) + SubmitCovenantSigs(covSigMsgs []*types.CovenantSigs) (*types.TxResponse, error) // QueryPendingDelegations queries BTC delegations that are in status of pending QueryPendingDelegations(limit uint64) ([]*types.Delegation, error) diff --git a/covenant/covenant.go b/covenant/covenant.go index c0eb791..0ae45c9 100644 --- a/covenant/covenant.go +++ b/covenant/covenant.go @@ -241,10 +241,10 @@ func (ce *CovenantEmulator) AddCovenantSignatures(btcDels []*types.Delegation) ( } // BuildCovenantSigs creates the covenant signatures from the signature response -func BuildCovenantSigs(pk *secp.PublicKey, resp SigningResponse) []types.CovenantSigs { - covenantSigs := make([]types.CovenantSigs, 0, len(resp.SignaturesByStkTxHash)) +func BuildCovenantSigs(pk *secp.PublicKey, resp SigningResponse) []*types.CovenantSigs { + covenantSigs := make([]*types.CovenantSigs, 0, len(resp.SignaturesByStkTxHash)) for stkTxHash, signatures := range resp.SignaturesByStkTxHash { - covenantSigs = append(covenantSigs, types.CovenantSigs{ + covenantSigs = append(covenantSigs, &types.CovenantSigs{ PublicKey: pk, StakingTxHash: stkTxHash, SlashingSigs: signatures.SlashSigs, @@ -608,8 +608,8 @@ func (ce *CovenantEmulator) Stop() error { } // SortCovenantSigs helper function to sort all covenant signatures by the staking tx hash -func SortCovenantSigs(covSigs []types.CovenantSigs) []types.CovenantSigs { - sorted := make([]types.CovenantSigs, len(covSigs)) +func SortCovenantSigs(covSigs []*types.CovenantSigs) []*types.CovenantSigs { + sorted := make([]*types.CovenantSigs, len(covSigs)) copy(sorted, covSigs) sort.SliceStable(sorted, func(i, j int) bool { return strings.Compare(sorted[i].StakingTxHash.String(), sorted[j].StakingTxHash.String()) == 1 diff --git a/covenant/covenant_test.go b/covenant/covenant_test.go index 46db9a5..01df1e0 100644 --- a/covenant/covenant_test.go +++ b/covenant/covenant_test.go @@ -61,7 +61,7 @@ func FuzzAddCovenantSig(f *testing.F) { require.NoError(t, err) numDels := datagen.RandomInt(r, 3) + 1 - covSigsSet := make([]types.CovenantSigs, 0, numDels) + covSigsSet := make([]*types.CovenantSigs, 0, numDels) btcDels := make([]*types.Delegation, 0, numDels) for i := 0; uint64(i) < numDels; i++ { // generate BTC delegation @@ -179,7 +179,7 @@ func FuzzAddCovenantSig(f *testing.F) { require.NoError(t, err) unbondingCovSlashingSigs = append(unbondingCovSlashingSigs, covenantSig.MustMarshal()) } - covSigsSet = append(covSigsSet, types.CovenantSigs{ + covSigsSet = append(covSigsSet, &types.CovenantSigs{ PublicKey: covKeyPair.PublicKey, StakingTxHash: testInfo.StakingTx.TxHash(), SlashingSigs: covSigs, diff --git a/testutil/mocks/babylon.go b/testutil/mocks/babylon.go index 879169f..6fd3173 100644 --- a/testutil/mocks/babylon.go +++ b/testutil/mocks/babylon.go @@ -79,7 +79,7 @@ func (mr *MockClientControllerMockRecorder) QueryStakingParamsByVersion(version } // SubmitCovenantSigs mocks base method. -func (m *MockClientController) SubmitCovenantSigs(covSigMsgs []types.CovenantSigs) (*types.TxResponse, error) { +func (m *MockClientController) SubmitCovenantSigs(covSigMsgs []*types.CovenantSigs) (*types.TxResponse, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "SubmitCovenantSigs", covSigMsgs) ret0, _ := ret[0].(*types.TxResponse) From ba6c3ee98be7e42aa486f338a1788d45552a5d17 Mon Sep 17 00:00:00 2001 From: RafilxTenfen Date: Thu, 10 Oct 2024 17:05:51 -0300 Subject: [PATCH 12/17] chore: rollback to pointer --- covenant/covenant.go | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/covenant/covenant.go b/covenant/covenant.go index 0ae45c9..257139d 100644 --- a/covenant/covenant.go +++ b/covenant/covenant.go @@ -218,14 +218,15 @@ func (ce *CovenantEmulator) AddCovenantSignatures(btcDels []*types.Delegation) ( } } + // 9. sign covenant transactions respSigs, err := ce.SignTransactions(signingReq) if err != nil { return nil, err } - covenantSigs := BuildCovenantSigs(ce.pk, *respSigs) + covenantSigs := BuildCovenantSigs(ce.pk, respSigs) - // 9. submit covenant sigs + // 10. submit covenant sigs res, err := ce.cc.SubmitCovenantSigs(covenantSigs) if err != nil { ce.recordMetricsFailedSignDelegations(len(covenantSigs)) @@ -241,7 +242,7 @@ func (ce *CovenantEmulator) AddCovenantSignatures(btcDels []*types.Delegation) ( } // BuildCovenantSigs creates the covenant signatures from the signature response -func BuildCovenantSigs(pk *secp.PublicKey, resp SigningResponse) []*types.CovenantSigs { +func BuildCovenantSigs(pk *secp.PublicKey, resp *SigningResponse) []*types.CovenantSigs { covenantSigs := make([]*types.CovenantSigs, 0, len(resp.SignaturesByStkTxHash)) for stkTxHash, signatures := range resp.SignaturesByStkTxHash { covenantSigs = append(covenantSigs, &types.CovenantSigs{ @@ -261,7 +262,6 @@ func (ce *CovenantEmulator) SignTransactions(signingReq map[chainhash.Hash]Signi startSignTime := time.Now() metricsTimeKeeper.SetPreviousSignStart(&startSignTime) - // 8. sign covenant transactions respSignatures, err := ce.signer.SignTransactions(SigningRequest{SigningTxsReqByStkTxHash: signingReq}) if err != nil { ce.recordMetricsFailedSignDelegations(len(signingReq)) @@ -608,6 +608,7 @@ func (ce *CovenantEmulator) Stop() error { } // SortCovenantSigs helper function to sort all covenant signatures by the staking tx hash +// Usefull for test checking expected inputs func SortCovenantSigs(covSigs []*types.CovenantSigs) []*types.CovenantSigs { sorted := make([]*types.CovenantSigs, len(covSigs)) copy(sorted, covSigs) From 7619dbcc212f22bc6b95ee74b8dda9fa43bf5b68 Mon Sep 17 00:00:00 2001 From: RafilxTenfen Date: Thu, 10 Oct 2024 17:12:32 -0300 Subject: [PATCH 13/17] chore: refactory signer signature handling --- keyring/signer.go | 90 +++++++++++++++++++++++++++++------------------ 1 file changed, 55 insertions(+), 35 deletions(-) diff --git a/keyring/signer.go b/keyring/signer.go index efe04ef..6ecea53 100644 --- a/keyring/signer.go +++ b/keyring/signer.go @@ -7,7 +7,9 @@ import ( "github.com/babylonlabs-io/babylon/btcstaking" "github.com/babylonlabs-io/covenant-emulator/covenant" + asig "github.com/babylonlabs-io/babylon/crypto/schnorr-adaptor-signature" "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcec/v2/schnorr" "github.com/btcsuite/btcd/chaincfg/chainhash" secp "github.com/decred/dcrd/dcrec/secp256k1/v4" ) @@ -74,51 +76,21 @@ func (kcs KeyringSigner) SignTransactions(req covenant.SigningRequest) (*covenan } for stakingTxHash, signingTxReq := range req.SigningTxsReqByStkTxHash { - // for each signing tx request - slashSigs := make([][]byte, 0, len(signingTxReq.FpEncKeys)) slashUnbondingSigs := make([][]byte, 0, len(signingTxReq.FpEncKeys)) for _, fpEncKey := range signingTxReq.FpEncKeys { - // creates slash sigs - // TODO: split to diff func - slashSig, err := btcstaking.EncSignTxWithOneScriptSpendInputStrict( - signingTxReq.SlashingTx, - signingTxReq.StakingTx, - signingTxReq.StakingOutputIdx, - signingTxReq.SlashingPkScriptPath, - covenantPrivKey, - fpEncKey, - ) + slashSig, slashUnbondingSig, err := slashUnbondSig(covenantPrivKey, signingTxReq, fpEncKey) if err != nil { - return nil, fmt.Errorf("failed to sign adaptor slash signature with finality provider public key %s: %w", fpEncKey.ToBytes(), err) + return nil, err } - slashSigs = append(slashSigs, slashSig.MustMarshal()) - // TODO: split to diff func - // creates slash unbonding sig - slashUnbondingSig, err := btcstaking.EncSignTxWithOneScriptSpendInputStrict( - signingTxReq.SlashUnbondingTx, - signingTxReq.UnbondingTx, - 0, // 0th output is always the unbonding script output - signingTxReq.UnbondingTxSlashingPkScriptPath, - covenantPrivKey, - fpEncKey, - ) - if err != nil { - return nil, fmt.Errorf("failed to sign adaptor slash unbonding signature with finality provider public key %s: %w", fpEncKey.ToBytes(), err) - } + slashSigs = append(slashSigs, slashSig.MustMarshal()) slashUnbondingSigs = append(slashUnbondingSigs, slashUnbondingSig.MustMarshal()) } - unbondingSig, err := btcstaking.SignTxWithOneScriptSpendInputStrict( - signingTxReq.UnbondingTx, - signingTxReq.StakingTx, - signingTxReq.StakingOutputIdx, - signingTxReq.StakingTxUnbondingPkScriptPath, - covenantPrivKey, - ) + unbondingSig, err := unbondSig(covenantPrivKey, signingTxReq) if err != nil { - return nil, fmt.Errorf("failed to sign unbonding tx: %w", err) + return nil, err } resp[stakingTxHash] = covenant.SignaturesResponse{ @@ -132,3 +104,51 @@ func (kcs KeyringSigner) SignTransactions(req covenant.SigningRequest) (*covenan SignaturesByStkTxHash: resp, }, nil } + +func slashUnbondSig( + covenantPrivKey *secp.PrivateKey, + signingTxReq covenant.SigningTxsRequest, + fpEncKey *asig.EncryptionKey, +) (slashSig, slashUnbondingSig *asig.AdaptorSignature, err error) { + // creates slash sigs + slashSig, err = btcstaking.EncSignTxWithOneScriptSpendInputStrict( + signingTxReq.SlashingTx, + signingTxReq.StakingTx, + signingTxReq.StakingOutputIdx, + signingTxReq.SlashingPkScriptPath, + covenantPrivKey, + fpEncKey, + ) + if err != nil { + return nil, nil, fmt.Errorf("failed to sign adaptor slash signature with finality provider public key %s: %w", fpEncKey.ToBytes(), err) + } + + // creates slash unbonding sig + slashUnbondingSig, err = btcstaking.EncSignTxWithOneScriptSpendInputStrict( + signingTxReq.SlashUnbondingTx, + signingTxReq.UnbondingTx, + 0, // 0th output is always the unbonding script output + signingTxReq.UnbondingTxSlashingPkScriptPath, + covenantPrivKey, + fpEncKey, + ) + if err != nil { + return nil, nil, fmt.Errorf("failed to sign adaptor slash unbonding signature with finality provider public key %s: %w", fpEncKey.ToBytes(), err) + } + + return slashSig, slashUnbondingSig, nil +} + +func unbondSig(covenantPrivKey *secp.PrivateKey, signingTxReq covenant.SigningTxsRequest) (*schnorr.Signature, error) { + unbondingSig, err := btcstaking.SignTxWithOneScriptSpendInputStrict( + signingTxReq.UnbondingTx, + signingTxReq.StakingTx, + signingTxReq.StakingOutputIdx, + signingTxReq.StakingTxUnbondingPkScriptPath, + covenantPrivKey, + ) + if err != nil { + return nil, fmt.Errorf("failed to sign unbonding tx: %w", err) + } + return unbondingSig, nil +} From d88b583c450743a3f5af399282888302ea2702be Mon Sep 17 00:00:00 2001 From: RafilxTenfen Date: Mon, 14 Oct 2024 09:37:00 -0300 Subject: [PATCH 14/17] chore: address gitferry pr comments --- cmd/covd/start.go | 12 +++++++++--- covenant/covenant.go | 3 +-- .../{expected_interfaces.go => expected_signer.go} | 0 3 files changed, 10 insertions(+), 5 deletions(-) rename covenant/{expected_interfaces.go => expected_signer.go} (100%) diff --git a/cmd/covd/start.go b/cmd/covd/start.go index cd800df..5ede96f 100644 --- a/cmd/covd/start.go +++ b/cmd/covd/start.go @@ -60,7 +60,7 @@ func start(ctx *cli.Context) error { pwd := ctx.String(passphraseFlag) - signer, err := NewSignerFromConfig(cfg, pwd) + signer, err := newSignerFromConfig(cfg, pwd) if err != nil { return fmt.Errorf("failed to create signer from config: %w", err) } @@ -84,6 +84,12 @@ func start(ctx *cli.Context) error { return srv.RunUntilShutdown() } -func NewSignerFromConfig(cfg *covcfg.Config, passphrase string) (*keyring.KeyringSigner, error) { - return keyring.NewKeyringSigner(cfg.BabylonConfig.ChainID, cfg.BabylonConfig.Key, cfg.BabylonConfig.KeyDirectory, cfg.BabylonConfig.KeyringBackend, passphrase) +func newSignerFromConfig(cfg *covcfg.Config, passphrase string) (*keyring.KeyringSigner, error) { + return keyring.NewKeyringSigner( + cfg.BabylonConfig.ChainID, + cfg.BabylonConfig.Key, + cfg.BabylonConfig.KeyDirectory, + cfg.BabylonConfig.KeyringBackend, + passphrase, + ) } diff --git a/covenant/covenant.go b/covenant/covenant.go index 257139d..5fd985d 100644 --- a/covenant/covenant.go +++ b/covenant/covenant.go @@ -54,7 +54,6 @@ type CovenantEmulator struct { func NewCovenantEmulator( config *covcfg.Config, cc clientcontroller.ClientController, - passphrase string, logger *zap.Logger, signer Signer, ) (*CovenantEmulator, error) { @@ -191,7 +190,7 @@ func (ce *CovenantEmulator) AddCovenantSignatures(btcDels []*types.Delegation) ( } // 8. Generate Signing Request - // Finality providers encription keys + // Finality providers encryption keys // pk script paths for Slash, unbond and unbonding slashing fpsEncKeys, err := fpEncKeysFromDel(btcDel) if err != nil { diff --git a/covenant/expected_interfaces.go b/covenant/expected_signer.go similarity index 100% rename from covenant/expected_interfaces.go rename to covenant/expected_signer.go From 2deb47a67b7a73a1ec75a97e5d73a09e52592981 Mon Sep 17 00:00:00 2001 From: RafilxTenfen Date: Mon, 14 Oct 2024 09:39:49 -0300 Subject: [PATCH 15/17] fix: pwd parameter --- cmd/covd/start.go | 2 +- covenant/covenant_test.go | 2 +- itest/test_manager.go | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/cmd/covd/start.go b/cmd/covd/start.go index 5ede96f..a9a8a39 100644 --- a/cmd/covd/start.go +++ b/cmd/covd/start.go @@ -65,7 +65,7 @@ func start(ctx *cli.Context) error { return fmt.Errorf("failed to create signer from config: %w", err) } - ce, err := covenant.NewCovenantEmulator(cfg, bbnClient, pwd, logger, signer) + ce, err := covenant.NewCovenantEmulator(cfg, bbnClient, logger, signer) if err != nil { return fmt.Errorf("failed to start the covenant emulator: %w", err) } diff --git a/covenant/covenant_test.go b/covenant/covenant_test.go index 01df1e0..e12f011 100644 --- a/covenant/covenant_test.go +++ b/covenant/covenant_test.go @@ -57,7 +57,7 @@ func FuzzAddCovenantSig(f *testing.F) { require.NoError(t, err) // create and start covenant emulator - ce, err := covenant.NewCovenantEmulator(&covenantConfig, mockClientController, passphrase, zap.NewNop(), signer) + ce, err := covenant.NewCovenantEmulator(&covenantConfig, mockClientController, zap.NewNop(), signer) require.NoError(t, err) numDels := datagen.RandomInt(r, 3) + 1 diff --git a/itest/test_manager.go b/itest/test_manager.go index 525840b..d2977f0 100644 --- a/itest/test_manager.go +++ b/itest/test_manager.go @@ -97,7 +97,7 @@ func StartManager(t *testing.T) *TestManager { signer, err := covdkeyring.NewKeyringSigner(covenantConfig.BabylonConfig.ChainID, covenantConfig.BabylonConfig.Key, covenantConfig.BabylonConfig.KeyDirectory, covenantConfig.BabylonConfig.KeyringBackend, passphrase) require.NoError(t, err) - ce, err := covenant.NewCovenantEmulator(covenantConfig, covbc, passphrase, logger, signer) + ce, err := covenant.NewCovenantEmulator(covenantConfig, covbc, logger, signer) require.NoError(t, err) err = ce.Start() require.NoError(t, err) From 97466b0c7c0395da8d48c271df791f1aaa443de5 Mon Sep 17 00:00:00 2001 From: RafilxTenfen Date: Mon, 14 Oct 2024 09:54:41 -0300 Subject: [PATCH 16/17] chore: modify to call the signed once per each delegation --- covenant/covenant.go | 49 ++++++++++++++----------------------- covenant/expected_signer.go | 13 ++-------- keyring/signer.go | 47 ++++++++++++++--------------------- 3 files changed, 39 insertions(+), 70 deletions(-) diff --git a/covenant/covenant.go b/covenant/covenant.go index 5fd985d..cc1415a 100644 --- a/covenant/covenant.go +++ b/covenant/covenant.go @@ -17,9 +17,7 @@ import ( "github.com/btcsuite/btcd/btcec/v2/schnorr" "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg" - "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/wire" - secp "github.com/decred/dcrd/dcrec/secp256k1/v4" "go.uber.org/zap" "github.com/babylonlabs-io/covenant-emulator/clientcontroller" @@ -88,7 +86,7 @@ func (ce *CovenantEmulator) AddCovenantSignatures(btcDels []*types.Delegation) ( return nil, fmt.Errorf("no delegations") } - signingReq := make(map[chainhash.Hash]SigningTxsRequest, len(btcDels)) + covenantSigs := make([]*types.CovenantSigs, 0, len(btcDels)) for _, btcDel := range btcDels { // 0. nil checks if btcDel == nil { @@ -204,7 +202,8 @@ func (ce *CovenantEmulator) AddCovenantSignatures(btcDels []*types.Delegation) ( continue } - signingReq[stakingTx.TxHash()] = SigningTxsRequest{ + // 9. sign covenant transactions + resp, err := ce.SignTransactions(SigningRequest{ StakingTx: stakingTx, SlashingTx: slashingTx, UnbondingTx: unbondingTx, @@ -214,19 +213,23 @@ func (ce *CovenantEmulator) AddCovenantSignatures(btcDels []*types.Delegation) ( StakingTxUnbondingPkScriptPath: stakingTxUnbondingPkScriptPath, UnbondingTxSlashingPkScriptPath: unbondingTxSlashingPkScriptPath, FpEncKeys: fpsEncKeys, + }) + if err != nil { + ce.logger.Error("failed to sign transactions", zap.Error(err)) + continue } - } - // 9. sign covenant transactions - respSigs, err := ce.SignTransactions(signingReq) - if err != nil { - return nil, err + covenantSigs = append(covenantSigs, &types.CovenantSigs{ + PublicKey: ce.pk, + StakingTxHash: stakingTx.TxHash(), + SlashingSigs: resp.SlashSigs, + UnbondingSig: resp.UnbondingSig, + SlashingUnbondingSigs: resp.SlashUnbondingSigs, + }) } - covenantSigs := BuildCovenantSigs(ce.pk, respSigs) - // 10. submit covenant sigs - res, err := ce.cc.SubmitCovenantSigs(covenantSigs) + res, err := ce.cc.SubmitCovenantSigs(SortCovenantSigs(covenantSigs)) if err != nil { ce.recordMetricsFailedSignDelegations(len(covenantSigs)) return nil, err @@ -240,30 +243,14 @@ func (ce *CovenantEmulator) AddCovenantSignatures(btcDels []*types.Delegation) ( return res, nil } -// BuildCovenantSigs creates the covenant signatures from the signature response -func BuildCovenantSigs(pk *secp.PublicKey, resp *SigningResponse) []*types.CovenantSigs { - covenantSigs := make([]*types.CovenantSigs, 0, len(resp.SignaturesByStkTxHash)) - for stkTxHash, signatures := range resp.SignaturesByStkTxHash { - covenantSigs = append(covenantSigs, &types.CovenantSigs{ - PublicKey: pk, - StakingTxHash: stkTxHash, - SlashingSigs: signatures.SlashSigs, - UnbondingSig: signatures.UnbondingSig, - SlashingUnbondingSigs: signatures.SlashUnbondingSigs, - }) - } - return SortCovenantSigs(covenantSigs) -} - // SignTransactions calls the signer and record metrics about signing -func (ce *CovenantEmulator) SignTransactions(signingReq map[chainhash.Hash]SigningTxsRequest) (*SigningResponse, error) { +func (ce *CovenantEmulator) SignTransactions(signingReq SigningRequest) (*SignaturesResponse, error) { // record metrics startSignTime := time.Now() metricsTimeKeeper.SetPreviousSignStart(&startSignTime) - respSignatures, err := ce.signer.SignTransactions(SigningRequest{SigningTxsReqByStkTxHash: signingReq}) + resp, err := ce.signer.SignTransactions(signingReq) if err != nil { - ce.recordMetricsFailedSignDelegations(len(signingReq)) return nil, err } @@ -272,7 +259,7 @@ func (ce *CovenantEmulator) SignTransactions(signingReq map[chainhash.Hash]Signi metricsTimeKeeper.SetPreviousSignFinish(&finishSignTime) timedSignDelegationLag.Observe(time.Since(startSignTime).Seconds()) - return respSignatures, nil + return resp, nil } func fpEncKeysFromDel(btcDel *types.Delegation) ([]*asig.EncryptionKey, error) { diff --git a/covenant/expected_signer.go b/covenant/expected_signer.go index 7701189..ff71cc2 100644 --- a/covenant/expected_signer.go +++ b/covenant/expected_signer.go @@ -3,7 +3,6 @@ package covenant import ( asig "github.com/babylonlabs-io/babylon/crypto/schnorr-adaptor-signature" "github.com/btcsuite/btcd/btcec/v2/schnorr" - "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/wire" secp "github.com/decred/dcrd/dcrec/secp256k1/v4" ) @@ -12,12 +11,12 @@ import ( type Signer interface { // SignTransactions signs all the transactions from the request // and returns all the signatures for Slash, Unbond and Unbonding Slash. - SignTransactions(req SigningRequest) (*SigningResponse, error) + SignTransactions(req SigningRequest) (*SignaturesResponse, error) // PubKey returns the current secp256k1 public key PubKey() (*secp.PublicKey, error) } -type SigningTxsRequest struct { +type SigningRequest struct { StakingTx *wire.MsgTx SlashingTx *wire.MsgTx UnbondingTx *wire.MsgTx @@ -29,16 +28,8 @@ type SigningTxsRequest struct { FpEncKeys []*asig.EncryptionKey } -type SigningRequest struct { - SigningTxsReqByStkTxHash map[chainhash.Hash]SigningTxsRequest -} - type SignaturesResponse struct { SlashSigs [][]byte UnbondingSig *schnorr.Signature SlashUnbondingSigs [][]byte } - -type SigningResponse struct { - SignaturesByStkTxHash map[chainhash.Hash]SignaturesResponse -} diff --git a/keyring/signer.go b/keyring/signer.go index 6ecea53..a53aa6d 100644 --- a/keyring/signer.go +++ b/keyring/signer.go @@ -10,7 +10,6 @@ import ( asig "github.com/babylonlabs-io/babylon/crypto/schnorr-adaptor-signature" "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcec/v2/schnorr" - "github.com/btcsuite/btcd/chaincfg/chainhash" secp "github.com/decred/dcrd/dcrec/secp256k1/v4" ) @@ -66,48 +65,40 @@ func (kcs KeyringSigner) getPrivKey() (*btcec.PrivateKey, error) { return privKey, nil } -// SignTransactions receives a batch of transactions to sign and returns all the signatures if nothing fails. -func (kcs KeyringSigner) SignTransactions(req covenant.SigningRequest) (*covenant.SigningResponse, error) { - resp := make(map[chainhash.Hash]covenant.SignaturesResponse, len(req.SigningTxsReqByStkTxHash)) - +// SignTransactions receives BTC delegation transactions to sign and returns all the signatures needed if nothing fails. +func (kcs KeyringSigner) SignTransactions(req covenant.SigningRequest) (*covenant.SignaturesResponse, error) { covenantPrivKey, err := kcs.getPrivKey() if err != nil { return nil, fmt.Errorf("failed to get Covenant private key: %w", err) } - for stakingTxHash, signingTxReq := range req.SigningTxsReqByStkTxHash { - slashSigs := make([][]byte, 0, len(signingTxReq.FpEncKeys)) - slashUnbondingSigs := make([][]byte, 0, len(signingTxReq.FpEncKeys)) - for _, fpEncKey := range signingTxReq.FpEncKeys { - slashSig, slashUnbondingSig, err := slashUnbondSig(covenantPrivKey, signingTxReq, fpEncKey) - if err != nil { - return nil, err - } - - slashSigs = append(slashSigs, slashSig.MustMarshal()) - slashUnbondingSigs = append(slashUnbondingSigs, slashUnbondingSig.MustMarshal()) - } - - unbondingSig, err := unbondSig(covenantPrivKey, signingTxReq) + slashSigs := make([][]byte, 0, len(req.FpEncKeys)) + slashUnbondingSigs := make([][]byte, 0, len(req.FpEncKeys)) + for _, fpEncKey := range req.FpEncKeys { + slashSig, slashUnbondingSig, err := slashUnbondSig(covenantPrivKey, req, fpEncKey) if err != nil { return nil, err } - resp[stakingTxHash] = covenant.SignaturesResponse{ - SlashSigs: slashSigs, - UnbondingSig: unbondingSig, - SlashUnbondingSigs: slashUnbondingSigs, - } + slashSigs = append(slashSigs, slashSig.MustMarshal()) + slashUnbondingSigs = append(slashUnbondingSigs, slashUnbondingSig.MustMarshal()) + } + + unbondingSig, err := unbondSig(covenantPrivKey, req) + if err != nil { + return nil, err } - return &covenant.SigningResponse{ - SignaturesByStkTxHash: resp, + return &covenant.SignaturesResponse{ + SlashSigs: slashSigs, + UnbondingSig: unbondingSig, + SlashUnbondingSigs: slashUnbondingSigs, }, nil } func slashUnbondSig( covenantPrivKey *secp.PrivateKey, - signingTxReq covenant.SigningTxsRequest, + signingTxReq covenant.SigningRequest, fpEncKey *asig.EncryptionKey, ) (slashSig, slashUnbondingSig *asig.AdaptorSignature, err error) { // creates slash sigs @@ -139,7 +130,7 @@ func slashUnbondSig( return slashSig, slashUnbondingSig, nil } -func unbondSig(covenantPrivKey *secp.PrivateKey, signingTxReq covenant.SigningTxsRequest) (*schnorr.Signature, error) { +func unbondSig(covenantPrivKey *secp.PrivateKey, signingTxReq covenant.SigningRequest) (*schnorr.Signature, error) { unbondingSig, err := btcstaking.SignTxWithOneScriptSpendInputStrict( signingTxReq.UnbondingTx, signingTxReq.StakingTx, From 9617ca43d9cbf1f8328fd66aa3aa7c55dc44b1f0 Mon Sep 17 00:00:00 2001 From: RafilxTenfen Date: Mon, 14 Oct 2024 12:25:48 -0300 Subject: [PATCH 17/17] chore: remove unecessary sorting of covenant signatures --- covenant/covenant.go | 16 +--------------- covenant/covenant_test.go | 3 +-- 2 files changed, 2 insertions(+), 17 deletions(-) diff --git a/covenant/covenant.go b/covenant/covenant.go index cc1415a..01c1332 100644 --- a/covenant/covenant.go +++ b/covenant/covenant.go @@ -3,8 +3,6 @@ package covenant import ( "encoding/hex" "fmt" - "sort" - "strings" "sync" "time" @@ -229,7 +227,7 @@ func (ce *CovenantEmulator) AddCovenantSignatures(btcDels []*types.Delegation) ( } // 10. submit covenant sigs - res, err := ce.cc.SubmitCovenantSigs(SortCovenantSigs(covenantSigs)) + res, err := ce.cc.SubmitCovenantSigs(covenantSigs) if err != nil { ce.recordMetricsFailedSignDelegations(len(covenantSigs)) return nil, err @@ -592,15 +590,3 @@ func (ce *CovenantEmulator) Stop() error { }) return stopErr } - -// SortCovenantSigs helper function to sort all covenant signatures by the staking tx hash -// Usefull for test checking expected inputs -func SortCovenantSigs(covSigs []*types.CovenantSigs) []*types.CovenantSigs { - sorted := make([]*types.CovenantSigs, len(covSigs)) - copy(sorted, covSigs) - sort.SliceStable(sorted, func(i, j int) bool { - return strings.Compare(sorted[i].StakingTxHash.String(), sorted[j].StakingTxHash.String()) == 1 - }) - - return sorted -} diff --git a/covenant/covenant_test.go b/covenant/covenant_test.go index e12f011..3d206a3 100644 --- a/covenant/covenant_test.go +++ b/covenant/covenant_test.go @@ -194,10 +194,9 @@ func FuzzAddCovenantSig(f *testing.F) { } btcDels = append(btcDels, invalidDelegation) - sortedCovSigs := covenant.SortCovenantSigs(covSigsSet) // check the sigs are expected expectedTxHash := testutil.GenRandomHexStr(r, 32) - mockClientController.EXPECT().SubmitCovenantSigs(sortedCovSigs). + mockClientController.EXPECT().SubmitCovenantSigs(covSigsSet). Return(&types.TxResponse{TxHash: expectedTxHash}, nil).AnyTimes() res, err := ce.AddCovenantSignatures(btcDels) require.NoError(t, err)