diff --git a/lnwallet/btcwallet/signer.go b/lnwallet/btcwallet/signer.go index 69f7ab60912..e2c0cd29651 100644 --- a/lnwallet/btcwallet/signer.go +++ b/lnwallet/btcwallet/signer.go @@ -238,13 +238,34 @@ func (b *BtcWallet) fetchPrivKey( // maybeTweakPrivKey examines the single and double tweak parameters on the // passed sign descriptor and may perform a mapping on the passed private key -// in order to utilize the tweaks, if populated. +// in order to utilize the tweaks, if populated. If both tweak parameters are +// set, then both are applied in the following order: +// +// a) double tweak +// b) single tweak func maybeTweakPrivKey(signDesc *input.SignDescriptor, privKey *btcec.PrivateKey) (*btcec.PrivateKey, error) { var retPriv *btcec.PrivateKey - switch { + // If both tweak parameters are set, then apply both. + if signDesc.DoubleTweak != nil && signDesc.SingleTweak != nil { + // First apply the double tweak. + retPriv = input.DeriveRevocationPrivKey( + privKey, signDesc.DoubleTweak, + ) + + // Then apply the single tweak. + retPriv = input.TweakPrivKey( + retPriv, signDesc.SingleTweak, + ) + + return retPriv, nil + } + + // If only one tweak parameter is set, apply it respectively over the + // provided private key. + switch { case signDesc.SingleTweak != nil: retPriv = input.TweakPrivKey(privKey, signDesc.SingleTweak) diff --git a/lnwallet/btcwallet/signer_test.go b/lnwallet/btcwallet/signer_test.go index e45211d82fe..2c511c4a418 100644 --- a/lnwallet/btcwallet/signer_test.go +++ b/lnwallet/btcwallet/signer_test.go @@ -350,3 +350,157 @@ func getChainBackend(t *testing.T, netParams *chaincfg.Params) (chain.Interface, func hardenedKey(part uint32) uint32 { return part + hdkeychain.HardenedKeyStart } + +// TestMaybeTweakPrivKey tests the maybeTweakPrivKey function to ensure it +// correctly applies single tweaks, double tweaks, both tweaks combined, and +// handles the case where no tweaks are applied. +func TestMaybeTweakPrivKey(t *testing.T) { + // Create a test private key. + privKeyBytes, err := hex.DecodeString( + "e68abc8e2a7a5b9f0e4a3c7d8b9e6f5" + + "a4b3c2d1e0f9e8d7c6b5a4938271605", + ) + require.NoError(t, err) + privKey, _ := btcec.PrivKeyFromBytes(privKeyBytes) + + // Create test tweak values. + singleTweakBytes, err := hex.DecodeString( + "1234567890abcdef1234567890abcdef" + + "1234567890abcdef1234567890abcdef", + ) + require.NoError(t, err) + + doubleTweakBytes, err := hex.DecodeString( + "fedcba0987654321fedcba0987654321" + + "fedcba0987654321fedcba0987654321", + ) + require.NoError(t, err) + doubleTweak, _ := btcec.PrivKeyFromBytes(doubleTweakBytes) + + testCases := []struct { + name string + singleTweak []byte + doubleTweak *btcec.PrivateKey + validate func(*testing.T, *btcec.PrivateKey) + }{ + { + name: "no tweaks applied", + singleTweak: nil, + doubleTweak: nil, + validate: func(t *testing.T, result *btcec.PrivateKey) { + // Should return the original private key + // unchanged. + require.Equal( + t, privKey.Serialize(), + result.Serialize(), + "expected private key to be unchanged", + ) + }, + }, + { + name: "single tweak only", + singleTweak: singleTweakBytes, + doubleTweak: nil, + validate: func(t *testing.T, result *btcec.PrivateKey) { + // Manually apply single tweak to verify. + expected := input.TweakPrivKey( + privKey, singleTweakBytes, + ) + require.Equal( + t, expected.Serialize(), + result.Serialize(), + "single tweak not applied correctly", + ) + // Ensure it's different from the original. + require.NotEqual( + t, privKey.Serialize(), + result.Serialize(), + "tweaked key should differ from "+ + "original", + ) + }, + }, + { + name: "double tweak only", + singleTweak: nil, + doubleTweak: doubleTweak, + validate: func(t *testing.T, result *btcec.PrivateKey) { + // Manually apply double tweak to verify. + expected := input.DeriveRevocationPrivKey( + privKey, doubleTweak, + ) + require.Equal( + t, expected.Serialize(), + result.Serialize(), + "double tweak not applied correctly", + ) + // Ensure it's different from the original. + require.NotEqual( + t, privKey.Serialize(), + result.Serialize(), + "tweaked key should differ from "+ + "original", + ) + }, + }, + { + name: "both tweaks combined", + singleTweak: singleTweakBytes, + doubleTweak: doubleTweak, + validate: func(t *testing.T, result *btcec.PrivateKey) { + // Manually apply both tweaks in order: double + // first, then single. + afterDouble := input.DeriveRevocationPrivKey( + privKey, doubleTweak, + ) + expected := input.TweakPrivKey( + afterDouble, singleTweakBytes, + ) + require.Equal( + t, expected.Serialize(), + result.Serialize(), + "combined tweaks not applied correctly", + ) + // Ensure it's different from single tweak only. + singleOnly := input.TweakPrivKey( + privKey, singleTweakBytes, + ) + require.NotEqual( + t, singleOnly.Serialize(), + result.Serialize(), + "combined tweak should differ from "+ + "single tweak only", + ) + // Ensure it's different from double tweak only. + doubleOnly := input.DeriveRevocationPrivKey( + privKey, doubleTweak, + ) + require.NotEqual( + t, doubleOnly.Serialize(), + result.Serialize(), + "combined tweak should differ from "+ + "double tweak only", + ) + }, + }, + } + + for _, tc := range testCases { + tc := tc + t.Run(tc.name, func(t *testing.T) { + // Create a sign descriptor with the test tweaks. + signDesc := &input.SignDescriptor{ + SingleTweak: tc.singleTweak, + DoubleTweak: tc.doubleTweak, + } + + // Call the function under test. + result, err := maybeTweakPrivKey(signDesc, privKey) + require.NoError(t, err) + require.NotNil(t, result) + + // Validate the result. + tc.validate(t, result) + }) + } +}