mirror of
https://github.com/lightningnetwork/lnd.git
synced 2025-03-04 09:48:19 +01:00
lnwallet+lnrpc: add derivation info for P2TR change addrs
In some situations (for example in Taproot Assets), we need to be able to prove that an address is a bare BIP-0086 address that doesn't commit to any script. We can do that by providing the BIP-0032 derivation info and internal key.
This commit is contained in:
parent
094fdbfa72
commit
fb20cd598e
4 changed files with 250 additions and 1 deletions
|
@ -1758,6 +1758,34 @@ func (w *WalletKit) handleChange(packet *psbt.Packet, changeIndex int32,
|
|||
return 0, fmt.Errorf("could not derive change script: %w", err)
|
||||
}
|
||||
|
||||
// We need to add the derivation info for the change address in case it
|
||||
// is a P2TR address. This is mostly to prove it's a bare BIP-0086
|
||||
// address, which is required for some protocols (such as Taproot
|
||||
// Assets).
|
||||
pOut := psbt.POutput{}
|
||||
_, isTaprootChangeAddr := changeAddr.(*btcutil.AddressTaproot)
|
||||
if isTaprootChangeAddr {
|
||||
changeAddrInfo, err := w.cfg.Wallet.AddressInfo(changeAddr)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("could not get address info: %w",
|
||||
err)
|
||||
}
|
||||
|
||||
deriv, trDeriv, _, err := btcwallet.Bip32DerivationFromAddress(
|
||||
changeAddrInfo,
|
||||
)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("could not get derivation info: "+
|
||||
"%w", err)
|
||||
}
|
||||
|
||||
pOut.TaprootInternalKey = trDeriv.XOnlyPubKey
|
||||
pOut.Bip32Derivation = []*psbt.Bip32Derivation{deriv}
|
||||
pOut.TaprootBip32Derivation = []*psbt.TaprootBip32Derivation{
|
||||
trDeriv,
|
||||
}
|
||||
}
|
||||
|
||||
newChangeIndex := int32(len(packet.Outputs))
|
||||
packet.UnsignedTx.TxOut = append(
|
||||
packet.UnsignedTx.TxOut, &wire.TxOut{
|
||||
|
@ -1765,7 +1793,7 @@ func (w *WalletKit) handleChange(packet *psbt.Packet, changeIndex int32,
|
|||
PkScript: changeScript,
|
||||
},
|
||||
)
|
||||
packet.Outputs = append(packet.Outputs, psbt.POutput{})
|
||||
packet.Outputs = append(packet.Outputs, pOut)
|
||||
|
||||
return newChangeIndex, nil
|
||||
}
|
||||
|
|
|
@ -9,12 +9,14 @@ import (
|
|||
"github.com/btcsuite/btcd/btcec/v2"
|
||||
"github.com/btcsuite/btcd/btcec/v2/schnorr"
|
||||
"github.com/btcsuite/btcd/btcutil"
|
||||
"github.com/btcsuite/btcd/btcutil/hdkeychain"
|
||||
"github.com/btcsuite/btcd/btcutil/psbt"
|
||||
"github.com/btcsuite/btcd/txscript"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/btcsuite/btcwallet/waddrmgr"
|
||||
"github.com/btcsuite/btcwallet/wallet"
|
||||
"github.com/lightningnetwork/lnd/input"
|
||||
"github.com/lightningnetwork/lnd/keychain"
|
||||
"github.com/lightningnetwork/lnd/lnwallet"
|
||||
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
|
||||
)
|
||||
|
@ -640,3 +642,78 @@ func (b *BtcWallet) lookupFirstCustomAccount(
|
|||
|
||||
return keyScope, account.AccountNumber, nil
|
||||
}
|
||||
|
||||
// Bip32DerivationFromKeyDesc returns the default and Taproot BIP-0032 key
|
||||
// derivation information from the given key descriptor information.
|
||||
func Bip32DerivationFromKeyDesc(keyDesc keychain.KeyDescriptor,
|
||||
coinType uint32) (*psbt.Bip32Derivation, *psbt.TaprootBip32Derivation,
|
||||
string) {
|
||||
|
||||
bip32Derivation := &psbt.Bip32Derivation{
|
||||
PubKey: keyDesc.PubKey.SerializeCompressed(),
|
||||
Bip32Path: []uint32{
|
||||
keychain.BIP0043Purpose + hdkeychain.HardenedKeyStart,
|
||||
coinType + hdkeychain.HardenedKeyStart,
|
||||
uint32(keyDesc.Family) +
|
||||
uint32(hdkeychain.HardenedKeyStart),
|
||||
0,
|
||||
keyDesc.Index,
|
||||
},
|
||||
}
|
||||
|
||||
derivationPath := fmt.Sprintf(
|
||||
"m/%d'/%d'/%d'/%d/%d", keychain.BIP0043Purpose, coinType,
|
||||
keyDesc.Family, 0, keyDesc.Index,
|
||||
)
|
||||
|
||||
return bip32Derivation, &psbt.TaprootBip32Derivation{
|
||||
XOnlyPubKey: bip32Derivation.PubKey[1:],
|
||||
MasterKeyFingerprint: bip32Derivation.MasterKeyFingerprint,
|
||||
Bip32Path: bip32Derivation.Bip32Path,
|
||||
LeafHashes: make([][]byte, 0),
|
||||
}, derivationPath
|
||||
}
|
||||
|
||||
// Bip32DerivationFromAddress returns the default and Taproot BIP-0032 key
|
||||
// derivation information from the given managed address.
|
||||
func Bip32DerivationFromAddress(
|
||||
addr waddrmgr.ManagedAddress) (*psbt.Bip32Derivation,
|
||||
*psbt.TaprootBip32Derivation, string, error) {
|
||||
|
||||
pubKeyAddr, ok := addr.(waddrmgr.ManagedPubKeyAddress)
|
||||
if !ok {
|
||||
return nil, nil, "", fmt.Errorf("address is not a pubkey " +
|
||||
"address")
|
||||
}
|
||||
|
||||
scope, derivationInfo, haveInfo := pubKeyAddr.DerivationInfo()
|
||||
if !haveInfo {
|
||||
return nil, nil, "", fmt.Errorf("address is an imported " +
|
||||
"public key, can't derive BIP32 path")
|
||||
}
|
||||
|
||||
bip32Derivation := &psbt.Bip32Derivation{
|
||||
PubKey: pubKeyAddr.PubKey().SerializeCompressed(),
|
||||
Bip32Path: []uint32{
|
||||
scope.Purpose + hdkeychain.HardenedKeyStart,
|
||||
scope.Coin + hdkeychain.HardenedKeyStart,
|
||||
derivationInfo.InternalAccount +
|
||||
hdkeychain.HardenedKeyStart,
|
||||
derivationInfo.Branch,
|
||||
derivationInfo.Index,
|
||||
},
|
||||
}
|
||||
|
||||
derivationPath := fmt.Sprintf(
|
||||
"m/%d'/%d'/%d'/%d/%d", scope.Purpose, scope.Coin,
|
||||
derivationInfo.InternalAccount, derivationInfo.Branch,
|
||||
derivationInfo.Index,
|
||||
)
|
||||
|
||||
return bip32Derivation, &psbt.TaprootBip32Derivation{
|
||||
XOnlyPubKey: bip32Derivation.PubKey[1:],
|
||||
MasterKeyFingerprint: bip32Derivation.MasterKeyFingerprint,
|
||||
Bip32Path: bip32Derivation.Bip32Path,
|
||||
LeafHashes: make([][]byte, 0),
|
||||
}, derivationPath, nil
|
||||
}
|
||||
|
|
|
@ -17,6 +17,7 @@ import (
|
|||
"github.com/btcsuite/btcwallet/waddrmgr"
|
||||
"github.com/lightningnetwork/lnd/input"
|
||||
"github.com/lightningnetwork/lnd/keychain"
|
||||
"github.com/lightningnetwork/lnd/lnwallet"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
|
@ -495,3 +496,136 @@ func TestEstimateInputWeight(t *testing.T) {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestBip32DerivationFromKeyDesc tests that we can correctly extract a BIP32
|
||||
// derivation path from a key descriptor.
|
||||
func TestBip32DerivationFromKeyDesc(t *testing.T) {
|
||||
privKey, err := btcec.NewPrivateKey()
|
||||
require.NoError(t, err)
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
keyDesc keychain.KeyDescriptor
|
||||
coinType uint32
|
||||
expectedPath string
|
||||
expectedBip32Path []uint32
|
||||
}{
|
||||
{
|
||||
name: "testnet multi-sig family",
|
||||
keyDesc: keychain.KeyDescriptor{
|
||||
PubKey: privKey.PubKey(),
|
||||
KeyLocator: keychain.KeyLocator{
|
||||
Family: keychain.KeyFamilyMultiSig,
|
||||
Index: 123,
|
||||
},
|
||||
},
|
||||
coinType: chaincfg.TestNet3Params.HDCoinType,
|
||||
expectedPath: "m/1017'/1'/0'/0/123",
|
||||
expectedBip32Path: []uint32{
|
||||
hardenedKey(keychain.BIP0043Purpose),
|
||||
hardenedKey(chaincfg.TestNet3Params.HDCoinType),
|
||||
hardenedKey(uint32(keychain.KeyFamilyMultiSig)),
|
||||
0, 123,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "mainnet watchtower family",
|
||||
keyDesc: keychain.KeyDescriptor{
|
||||
PubKey: privKey.PubKey(),
|
||||
KeyLocator: keychain.KeyLocator{
|
||||
Family: keychain.KeyFamilyTowerSession,
|
||||
Index: 456,
|
||||
},
|
||||
},
|
||||
coinType: chaincfg.MainNetParams.HDCoinType,
|
||||
expectedPath: "m/1017'/0'/8'/0/456",
|
||||
expectedBip32Path: []uint32{
|
||||
hardenedKey(keychain.BIP0043Purpose),
|
||||
hardenedKey(chaincfg.MainNetParams.HDCoinType),
|
||||
hardenedKey(
|
||||
uint32(keychain.KeyFamilyTowerSession),
|
||||
),
|
||||
0, 456,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
tc := tc
|
||||
|
||||
t.Run(tc.name, func(tt *testing.T) {
|
||||
d, trD, path := Bip32DerivationFromKeyDesc(
|
||||
tc.keyDesc, tc.coinType,
|
||||
)
|
||||
require.NoError(tt, err)
|
||||
|
||||
require.Equal(tt, tc.expectedPath, path)
|
||||
require.Equal(tt, tc.expectedBip32Path, d.Bip32Path)
|
||||
require.Equal(tt, tc.expectedBip32Path, trD.Bip32Path)
|
||||
|
||||
serializedKey := tc.keyDesc.PubKey.SerializeCompressed()
|
||||
require.Equal(tt, serializedKey, d.PubKey)
|
||||
require.Equal(tt, serializedKey[1:], trD.XOnlyPubKey)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestBip32DerivationFromAddress tests that we can correctly extract a BIP32
|
||||
// derivation path from an address.
|
||||
func TestBip32DerivationFromAddress(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
addrType lnwallet.AddressType
|
||||
expectedAddr string
|
||||
expectedPath string
|
||||
expectedBip32Path []uint32
|
||||
expectedPubKey string
|
||||
}{
|
||||
{
|
||||
name: "p2wkh",
|
||||
addrType: lnwallet.WitnessPubKey,
|
||||
expectedAddr: firstAddress,
|
||||
expectedPath: "m/84'/0'/0'/0/0",
|
||||
expectedBip32Path: []uint32{
|
||||
hardenedKey(waddrmgr.KeyScopeBIP0084.Purpose),
|
||||
hardenedKey(0), hardenedKey(0), 0, 0,
|
||||
},
|
||||
expectedPubKey: firstAddressPubKey,
|
||||
},
|
||||
{
|
||||
name: "p2tr",
|
||||
addrType: lnwallet.TaprootPubkey,
|
||||
expectedAddr: firstAddressTaproot,
|
||||
expectedPath: "m/86'/0'/0'/0/0",
|
||||
expectedBip32Path: []uint32{
|
||||
hardenedKey(waddrmgr.KeyScopeBIP0086.Purpose),
|
||||
hardenedKey(0), hardenedKey(0), 0, 0,
|
||||
},
|
||||
expectedPubKey: firstAddressTaprootPubKey,
|
||||
},
|
||||
}
|
||||
|
||||
w, _ := newTestWallet(t, netParams, seedBytes)
|
||||
for _, tc := range testCases {
|
||||
tc := tc
|
||||
|
||||
addr, err := w.NewAddress(
|
||||
tc.addrType, false, lnwallet.DefaultAccountName,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, tc.expectedAddr, addr.String())
|
||||
|
||||
addrInfo, err := w.AddressInfo(addr)
|
||||
require.NoError(t, err)
|
||||
managedAddr, ok := addrInfo.(waddrmgr.ManagedPubKeyAddress)
|
||||
require.True(t, ok)
|
||||
|
||||
d, trD, path, err := Bip32DerivationFromAddress(managedAddr)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, tc.expectedPath, path)
|
||||
require.Equal(t, tc.expectedBip32Path, d.Bip32Path)
|
||||
require.Equal(t, tc.expectedBip32Path, trD.Bip32Path)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -38,11 +38,21 @@ var (
|
|||
// which is a special case for the BIP49/84 addresses in btcwallet).
|
||||
firstAddress = "bcrt1qgdlgjc5ede7fjv350wcjqat80m0zsmfaswsj9p"
|
||||
|
||||
// firstAddressPubKey is the public key of the first address that we
|
||||
// should get from the wallet.
|
||||
firstAddressPubKey = "02b844aecf8250c29e46894147a7dae02de55a034a533b6" +
|
||||
"0c6a6469294ee356ce4"
|
||||
|
||||
// firstAddressTaproot is the first address that we should get from the
|
||||
// wallet when deriving a taproot address.
|
||||
firstAddressTaproot = "bcrt1ps8c222fgysvnsj2m8hxk8khy6wthcrhv9va9z3t4" +
|
||||
"h3qeyz65sh4qqwvdgc"
|
||||
|
||||
// firstAddressTaprootPubKey is the public key of the first address that
|
||||
// we should get from the wallet when deriving a taproot address.
|
||||
firstAddressTaprootPubKey = "03004113d6185c955d6e8f5922b50cc0ac3b64fa" +
|
||||
"0979402604c5b887f07e3b5388"
|
||||
|
||||
testPubKeyBytes, _ = hex.DecodeString(
|
||||
"037a67771635344641d4b56aac33cd5f7a265b59678dce3aec31b89125e3" +
|
||||
"b8b9b2",
|
||||
|
|
Loading…
Add table
Reference in a new issue