From 8f80d0e8b4e7955216f5cc5bfc9bb1e572880186 Mon Sep 17 00:00:00 2001 From: Oliver Gugger Date: Thu, 23 Feb 2023 11:19:27 +0100 Subject: [PATCH] closepoolaccount: add compatibility for p2tr accounts --- cmd/chantools/closepoolaccount.go | 309 +++++++++++++++++++------ cmd/chantools/closepoolaccount_test.go | 98 ++++++++ cmd/chantools/testdata/wallet.db | Bin 106496 -> 106496 bytes go.mod | 8 +- go.sum | 18 +- lnd/signer.go | 59 ++++- 6 files changed, 413 insertions(+), 79 deletions(-) create mode 100644 cmd/chantools/closepoolaccount_test.go diff --git a/cmd/chantools/closepoolaccount.go b/cmd/chantools/closepoolaccount.go index ab45675..3817822 100644 --- a/cmd/chantools/closepoolaccount.go +++ b/cmd/chantools/closepoolaccount.go @@ -6,11 +6,13 @@ import ( "fmt" "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcec/v2/schnorr" "github.com/btcsuite/btcd/btcutil/hdkeychain" "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/wire" "github.com/guggero/chantools/btc" "github.com/guggero/chantools/lnd" + "github.com/lightninglabs/pool/account" "github.com/lightninglabs/pool/poolscript" "github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/keychain" @@ -23,14 +25,12 @@ const ( defaultMaxNumBlocks = 200000 defaultMaxNumAccounts = 20 defaultMaxNumBatchKeys = 500 + oddByte = input.PubKeyFormatCompressedOdd ) var ( - initialBatchKeyBytes, _ = hex.DecodeString( - "02824d0cbac65e01712124c50ff2cc74ce22851d7b444c1bf2ae66afefb8" + - "eaf27f", - ) - initialBatchKey, _ = btcec.ParsePubKey(initialBatchKeyBytes) + initialBatchKeyBytes, _ = hex.DecodeString(account.InitialBatchKey) + initialBatchKey, _ = btcec.ParsePubKey(initialBatchKeyBytes) mainnetAuctioneerKeyHex = "028e87bdd134238f8347f845d9ecc827b843d0d1e2" + "7cdcb46da704d916613f4fce" @@ -186,6 +186,11 @@ func closePoolAccount(extendedKey *hdkeychain.ExtendedKey, apiURL string, log.Debugf("Brute forcing pk script %x for outpoint %v", pkScript, outpoint) + script, err := txscript.ParsePkScript(pkScript) + if err != nil { + return fmt.Errorf("error parsing pk script: %w", err) + } + // Let's derive the account key family's extended key first. path := []uint32{ lnd.HardenedKeyStart + uint32(keychain.BIP0043Purpose), @@ -199,7 +204,22 @@ func closePoolAccount(extendedKey *hdkeychain.ExtendedKey, apiURL string, } // Try our luck. - acct, err := bruteForceAccountScript( + var ( + acct *poolAccount + accountVersion account.Version + ) + switch script.Class() { + case txscript.WitnessV0ScriptHashTy: + accountVersion = account.VersionInitialNoVersion + + case txscript.WitnessV1TaprootTy: + accountVersion = account.VersionTaprootEnabled + + default: + return fmt.Errorf("unsupported script class %v", script.Class()) + } + + acct, err = bruteForceAccountScript( accountBaseKey, auctioneerKey, minExpiry, maxNumBlocks, maxNumAccounts, maxNumBatchKeys, pkScript, ) @@ -220,8 +240,40 @@ func closePoolAccount(extendedKey *hdkeychain.ExtendedKey, apiURL string, // Calculate the fee based on the given fee rate and our weight // estimation. - var estimator input.TxWeightEstimator - estimator.AddWitnessInput(input.ToLocalTimeoutWitnessSize) + var ( + estimator input.TxWeightEstimator + signDesc = &input.SignDescriptor{ + KeyDesc: keychain.KeyDescriptor{ + KeyLocator: keychain.KeyLocator{ + Family: poolscript.AccountKeyFamily, + Index: acct.keyIndex, + }, + }, + SingleTweak: acct.keyTweak, + WitnessScript: acct.witnessScript, + Output: &wire.TxOut{ + PkScript: pkScript, + Value: sweepValue, + }, + InputIndex: 0, + PrevOutputFetcher: txscript.NewCannedPrevOutputFetcher( + pkScript, sweepValue, + ), + } + ) + + switch accountVersion { + case account.VersionInitialNoVersion: + estimator.AddWitnessInput(poolscript.ExpiryWitnessSize) + signDesc.HashType = txscript.SigHashAll + signDesc.SignMethod = input.WitnessV0SignMethod + signDesc.SigHashes = input.NewTxSigHashesV0Only(sweepTx) + + case account.VersionTaprootEnabled: + estimator.AddWitnessInput(poolscript.TaprootExpiryWitnessSize) + signDesc.HashType = txscript.SigHashDefault + signDesc.SignMethod = input.TaprootScriptSpendSignMethod + } estimator.AddP2WKHOutput() feeRateKWeight := chainfee.SatPerKVByte(1000 * feeRate).FeePerKWeight() totalFee := feeRateKWeight.FeeForWeight(int64(estimator.Weight())) @@ -240,32 +292,23 @@ func closePoolAccount(extendedKey *hdkeychain.ExtendedKey, apiURL string, totalFee, sweepValue, estimator.Weight()) // Create the sign descriptor for the input then sign the transaction. - sigHashes := input.NewTxSigHashesV0Only(sweepTx) - signDesc := &input.SignDescriptor{ - KeyDesc: keychain.KeyDescriptor{ - KeyLocator: keychain.KeyLocator{ - Family: poolscript.AccountKeyFamily, - Index: acct.keyIndex, - }, - }, - SingleTweak: acct.keyTweak, - WitnessScript: acct.witnessScript, - Output: &wire.TxOut{ - PkScript: pkScript, - Value: sweepValue, - }, - InputIndex: 0, - SigHashes: sigHashes, - HashType: txscript.SigHashAll, - } sig, err := signer.SignOutputRaw(sweepTx, signDesc) if err != nil { return fmt.Errorf("error signing sweep tx: %w", err) } - ourSig := append(sig.Serialize(), byte(signDesc.HashType)) - sweepTx.TxIn[0].Witness = poolscript.SpendExpiry( - acct.witnessScript, ourSig, - ) + + switch accountVersion { + case account.VersionInitialNoVersion: + ourSig := append(sig.Serialize(), byte(signDesc.HashType)) + sweepTx.TxIn[0].Witness = poolscript.SpendExpiry( + acct.witnessScript, ourSig, + ) + + case account.VersionTaprootEnabled: + sweepTx.TxIn[0].Witness = poolscript.SpendExpiryTaproot( + acct.witnessScript, sig.Serialize(), acct.controlBlock, + ) + } var buf bytes.Buffer err = sweepTx.Serialize(&buf) @@ -296,20 +339,22 @@ type poolAccount struct { batchKey []byte keyTweak []byte witnessScript []byte + controlBlock []byte + version poolscript.Version } func (a *poolAccount) String() string { return fmt.Sprintf("key_index=%d, expiry=%d, shared_key=%x, "+ - "batch_key=%x, key_tweak=%x, witness_script=%x", + "batch_key=%x, key_tweak=%x, witness_script=%x, version=%d", a.keyIndex, a.expiry, a.sharedKey[:], a.batchKey, a.keyTweak, - a.witnessScript) + a.witnessScript, a.version) } func bruteForceAccountScript(accountBaseKey *hdkeychain.ExtendedKey, - auctioneerKey *btcec.PublicKey, minExpiry, maxNumBlocks, maxNumAccounts, + auctioneerKey *btcec.PublicKey, minExpiry, maxExpiry, maxNumAccounts, maxNumBatchKeys uint32, targetScript []byte) (*poolAccount, error) { - // The outer-most loop is over the possible accounts. + // The outermost loop is over the possible accounts. for i := uint32(0); i < maxNumAccounts; i++ { accountExtendedKey, err := accountBaseKey.DeriveNonStandard(i) if err != nil { @@ -337,36 +382,31 @@ func bruteForceAccountScript(accountBaseKey *hdkeychain.ExtendedKey, for batchKeyIndex < maxNumBatchKeys { // And then finally the loop over the actual account // expiry in blocks. - block, err := fastScript( - minExpiry, maxNumBlocks, + acct, err := fastScript( + i, minExpiry, maxExpiry, accountPrivKey.PubKey(), auctioneerKey, currentBatchKey, sharedKey, targetScript, ) if err == nil { - witnessScript, err := poolscript.AccountWitnessScript( - block, accountPrivKey.PubKey(), - auctioneerKey, currentBatchKey, - sharedKey, - ) - if err != nil { - return nil, fmt.Errorf("error "+ - "deriving script: %w", err) - } - - traderKeyTweak := poolscript.TraderKeyTweak( - currentBatchKey, sharedKey, - accountPrivKey.PubKey(), - ) - - batchKey := currentBatchKey.SerializeCompressed() - return &poolAccount{ - keyIndex: i, - expiry: block, - sharedKey: sharedKey, - batchKey: batchKey, - keyTweak: traderKeyTweak, - witnessScript: witnessScript, - }, nil + return acct, nil + } + acct, err = fastScriptTaproot( + poolscript.VersionTaprootMuSig2, i, minExpiry, + maxExpiry, accountPrivKey.PubKey(), + auctioneerKey, currentBatchKey, sharedKey, + targetScript, + ) + if err == nil { + return acct, nil + } + acct, err = fastScriptTaproot( + poolscript.VersionTaprootMuSig2V100RC2, i, + minExpiry, maxExpiry, + accountPrivKey.PubKey(), auctioneerKey, + currentBatchKey, sharedKey, targetScript, + ) + if err == nil { + return acct, nil } currentBatchKey = poolscript.IncrementKey( @@ -381,13 +421,25 @@ func bruteForceAccountScript(accountBaseKey *hdkeychain.ExtendedKey, return nil, fmt.Errorf("account script not derived") } -func fastScript(expiryFrom, expiryTo uint32, traderKey, auctioneerKey, +func fastScript(keyIndex, expiryFrom, expiryTo uint32, traderKey, auctioneerKey, batchKey *btcec.PublicKey, secret [32]byte, - targetScript []byte) (uint32, error) { + targetScript []byte) (*poolAccount, error) { + + script, err := txscript.ParsePkScript(targetScript) + if err != nil { + return nil, err + } + if script.Class() != txscript.WitnessV0ScriptHashTy { + return nil, fmt.Errorf("incompatible script class") + } traderKeyTweak := poolscript.TraderKeyTweak(batchKey, secret, traderKey) - tweakedTraderKey := input.TweakPubKeyWithTweak(traderKey, traderKeyTweak) - tweakedAuctioneerKey := input.TweakPubKey(auctioneerKey, tweakedTraderKey) + tweakedTraderKey := input.TweakPubKeyWithTweak( + traderKey, traderKeyTweak, + ) + tweakedAuctioneerKey := input.TweakPubKey( + auctioneerKey, tweakedTraderKey, + ) for block := expiryFrom; block <= expiryTo; block++ { builder := txscript.NewScriptBuilder() @@ -406,17 +458,136 @@ func fastScript(expiryFrom, expiryTo uint32, traderKey, auctioneerKey, currentScript, err := builder.Script() if err != nil { - return 0, fmt.Errorf("error building script: %w", err) + return nil, fmt.Errorf("error building script: %w", err) } currentPkScript, err := input.WitnessScriptHash(currentScript) if err != nil { - return 0, fmt.Errorf("error hashing script: %w", err) + return nil, fmt.Errorf("error hashing script: %w", err) } - if bytes.Equal(currentPkScript, targetScript) { - return block, nil + if !bytes.Equal(currentPkScript, targetScript) { + continue } + + return &poolAccount{ + keyIndex: keyIndex, + expiry: block, + sharedKey: secret, + batchKey: batchKey.SerializeCompressed(), + keyTweak: traderKeyTweak, + witnessScript: currentScript, + version: poolscript.VersionWitnessScript, + }, nil } - return 0, fmt.Errorf("account script not derived") + return nil, fmt.Errorf("account script not derived") +} + +func fastScriptTaproot(scriptVersion poolscript.Version, keyIndex, expiryFrom, + expiryTo uint32, traderKey, auctioneerKey, batchKey *btcec.PublicKey, + secret [32]byte, targetScript []byte) (*poolAccount, error) { + + parsedScript, err := txscript.ParsePkScript(targetScript) + if err != nil { + return nil, err + } + if parsedScript.Class() != txscript.WitnessV1TaprootTy { + return nil, fmt.Errorf("incompatible script class") + } + + traderKeyTweak := poolscript.TraderKeyTweak(batchKey, secret, traderKey) + tweakedTraderKey := input.TweakPubKeyWithTweak( + traderKey, traderKeyTweak, + ) + + var muSig2Version input.MuSig2Version + switch scriptVersion { + // The v0.4.0 MuSig2 implementation requires the keys to be serialized + // using the Schnorr (32-byte x-only) serialization format. + case poolscript.VersionTaprootMuSig2: + muSig2Version = input.MuSig2Version040 + + var err error + auctioneerKey, err = schnorr.ParsePubKey( + schnorr.SerializePubKey(auctioneerKey), + ) + if err != nil { + return nil, fmt.Errorf("error parsing auctioneer key: "+ + "%w", err) + } + + traderKey, err = schnorr.ParsePubKey( + schnorr.SerializePubKey(traderKey), + ) + if err != nil { + return nil, fmt.Errorf("error parsing trader key: %w", + err) + } + + // The v1.0.0-rc2 MuSig2 implementation works with the regular, 33-byte + // compressed keys, so we can just pass them in as they are. + case poolscript.VersionTaprootMuSig2V100RC2: + muSig2Version = input.MuSig2Version100RC2 + + default: + return nil, fmt.Errorf("invalid account version <%d>", + scriptVersion) + } + + for block := expiryFrom; block <= expiryTo; block++ { + builder := txscript.NewScriptBuilder() + + builder.AddData(schnorr.SerializePubKey(tweakedTraderKey)) + builder.AddOp(txscript.OP_CHECKSIGVERIFY) + + builder.AddInt64(int64(block)) + builder.AddOp(txscript.OP_CHECKLOCKTIMEVERIFY) + + script, err := builder.Script() + if err != nil { + return nil, err + } + + rootHash := txscript.NewBaseTapLeaf(script).TapHash() + aggregateKey, err := input.MuSig2CombineKeys( + muSig2Version, []*btcec.PublicKey{ + auctioneerKey, traderKey, + }, true, &input.MuSig2Tweaks{ + TaprootTweak: rootHash[:], + }, + ) + if err != nil { + return nil, fmt.Errorf("error combining keys: %w", err) + } + + currentKey := schnorr.SerializePubKey(aggregateKey.FinalKey) + if !bytes.Equal(currentKey, targetScript[2:]) { + continue + } + + odd := aggregateKey.FinalKey.SerializeCompressed()[0] == oddByte + controlBlock := txscript.ControlBlock{ + InternalKey: aggregateKey.PreTweakedKey, + LeafVersion: txscript.BaseLeafVersion, + OutputKeyYIsOdd: odd, + } + blockBytes, err := controlBlock.ToBytes() + if err != nil { + return nil, fmt.Errorf("error serializing control "+ + "block: %w", err) + } + + return &poolAccount{ + keyIndex: keyIndex, + expiry: block, + sharedKey: secret, + batchKey: batchKey.SerializeCompressed(), + keyTweak: traderKeyTweak, + witnessScript: script, + controlBlock: blockBytes, + version: scriptVersion, + }, nil + } + + return nil, fmt.Errorf("account script not derived") } diff --git a/cmd/chantools/closepoolaccount_test.go b/cmd/chantools/closepoolaccount_test.go new file mode 100644 index 0000000..edade66 --- /dev/null +++ b/cmd/chantools/closepoolaccount_test.go @@ -0,0 +1,98 @@ +package main + +import ( + "encoding/hex" + "testing" + + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcutil/hdkeychain" + "github.com/btcsuite/btcd/chaincfg" + "github.com/guggero/chantools/lnd" + "github.com/lightninglabs/pool/poolscript" + "github.com/lightningnetwork/lnd/keychain" + "github.com/stretchr/testify/require" +) + +type testAccount struct { + name string + rootKey string + pkScript string + minExpiry uint32 +} + +var ( + auctioneerKeyBytes, _ = hex.DecodeString( + "0353c7c0d3258c4957331b86af335568232e9af8df61330cee3a7488b61c" + + "f6c298", + ) + auctioneerKey, _ = btcec.ParsePubKey(auctioneerKeyBytes) + + testAccounts = []testAccount{{ + name: "regtest taproot (v1)", + rootKey: "tprv8ZgxMBicQKsPdkvdLKn7HG2hhZ9Ewsgze1Yj3KDEcvb6H5U" + + "519UtfoPPP3hYVgFTn7hXmvE41qaugbaYiZN8wM1HoQHhs3AzSwg" + + "xGYdD8gM", + pkScript: "512001e8d17b83358476534aae4eae2062ea9025dfd858cd81" + + "7bac5f439969da92a6", + minExpiry: 1600, + }, { + name: "regtest taproot (v2)", + rootKey: "tprv8ZgxMBicQKsPdkvdLKn7HG2hhZ9Ewsgze1Yj3KDEcvb6H5U" + + "519UtfoPPP3hYVgFTn7hXmvE41qaugbaYiZN8wM1HoQHhs3AzSwg" + + "xGYdD8gM", + pkScript: "51209dfee24b87f5c35d5a310496a64fab70641bd03d40d5cc" + + "3720f6061f7435778a", + minExpiry: 2060, + }, { + name: "regtest segwit (v0)", + rootKey: "tprv8ZgxMBicQKsPdkvdLKn7HG2hhZ9Ewsgze1Yj3KDEcvb6H5U" + + "519UtfoPPP3hYVgFTn7hXmvE41qaugbaYiZN8wM1HoQHhs3AzSwg" + + "xGYdD8gM", + pkScript: "00201acfd449370aca0f744141bc6fe1f9fe326aa57a9cd35f" + + "bc2f8f15af4c0f4597", + minExpiry: 1600, + }} +) + +func TestClosePoolAccount(t *testing.T) { + t.Parallel() + + path := []uint32{ + lnd.HardenedKeyStart + uint32(keychain.BIP0043Purpose), + lnd.HardenedKeyStart + chaincfg.RegressionNetParams.HDCoinType, + lnd.HardenedKeyStart + uint32(poolscript.AccountKeyFamily), + 0, + } + const ( + maxBlocks = 50 + maxAccounts = 5 + maxBatchKeys = 10 + ) + + for _, tc := range testAccounts { + tc := tc + + t.Run(tc.name, func(tt *testing.T) { + tt.Parallel() + + extendedKey, err := hdkeychain.NewKeyFromString( + tc.rootKey, + ) + require.NoError(tt, err) + accountBaseKey, err := lnd.DeriveChildren( + extendedKey, path, + ) + require.NoError(tt, err) + targetScriptBytes, err := hex.DecodeString(tc.pkScript) + require.NoError(tt, err) + + acct, err := bruteForceAccountScript( + accountBaseKey, auctioneerKey, tc.minExpiry, + tc.minExpiry+maxBlocks, maxAccounts, + maxBatchKeys, targetScriptBytes, + ) + require.NoError(tt, err) + t.Logf("Found account: %v", acct) + }) + } +} diff --git a/cmd/chantools/testdata/wallet.db b/cmd/chantools/testdata/wallet.db index be28e87b283091cb7cd3562dc3fc7589fe9f61ec..69dc1e868dda10aac8b3fb4ebc77f257e7474b4b 100644 GIT binary patch delta 61 zcmZoTz}9epZGwZqA_fSson;gF_k5%LX2$~?_$LWC2rPz5s6Se39(W|V3M|p2uuOrm F0RUX46e0is delta 61 zcmZoTz}9epZGwZq0tN`E*ON}?k1LPe?08@U|0DqifrU_sr@rSE?alIRz!FUg%M=(J E03E^-AOHXW diff --git a/go.mod b/go.mod index 601cefa..0e758a4 100644 --- a/go.mod +++ b/go.mod @@ -17,8 +17,8 @@ require ( github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 github.com/gogo/protobuf v1.3.2 github.com/hasura/go-graphql-client v0.9.1 - github.com/lightninglabs/pool v0.5.7-alpha.0.20220715160511-f7c1ef26af2b - github.com/lightningnetwork/lnd v0.16.0-beta.rc2 + github.com/lightninglabs/pool v0.6.2-beta.0.20230329135228-c3bffb52df3a + github.com/lightningnetwork/lnd v0.16.0-beta github.com/lightningnetwork/lnd/kvdb v1.4.1 github.com/lightningnetwork/lnd/queue v1.1.0 github.com/lightningnetwork/lnd/ticker v1.1.0 @@ -56,8 +56,8 @@ require ( github.com/go-errors/errors v1.0.1 // indirect github.com/go-logr/logr v1.2.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect - github.com/gofrs/uuid v4.2.0+incompatible // indirect github.com/golang-jwt/jwt/v4 v4.4.2 // indirect + github.com/golang/mock v1.6.0 // indirect github.com/golang/protobuf v1.5.2 // indirect github.com/golang/snappy v0.0.4 // indirect github.com/google/btree v1.0.1 // indirect @@ -94,8 +94,10 @@ require ( github.com/klauspost/pgzip v1.2.5 // indirect github.com/lib/pq v1.10.3 // indirect github.com/lightninglabs/gozmq v0.0.0-20191113021534-d20a764486bf // indirect + github.com/lightninglabs/lndclient v0.16.0-10 // indirect github.com/lightninglabs/neutrino v0.15.0 // indirect github.com/lightninglabs/neutrino/cache v1.1.1 // indirect + github.com/lightninglabs/pool/auctioneerrpc v1.0.7 // indirect github.com/lightningnetwork/lightning-onion v1.2.1-0.20221202012345-ca23184850a1 // indirect github.com/lightningnetwork/lnd/clock v1.1.0 // indirect github.com/lightningnetwork/lnd/healthcheck v1.2.2 // indirect diff --git a/go.sum b/go.sum index 6a008f0..ba7ea09 100644 --- a/go.sum +++ b/go.sum @@ -223,7 +223,6 @@ github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/E github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gofrs/uuid v4.2.0+incompatible h1:yyYWMnhkhrKwwr8gAOcOCYxOOscHgDS9yZgBrnJfGa0= -github.com/gofrs/uuid v4.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= @@ -244,6 +243,8 @@ github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= +github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= +github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= @@ -464,16 +465,20 @@ github.com/lib/pq v1.10.3 h1:v9QZf2Sn6AmjXtQeFpdoq/eaNtYP6IN+7lcrygsIAtg= github.com/lib/pq v1.10.3/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lightninglabs/gozmq v0.0.0-20191113021534-d20a764486bf h1:HZKvJUHlcXI/f/O0Avg7t8sqkPo78HFzjmeYFl6DPnc= github.com/lightninglabs/gozmq v0.0.0-20191113021534-d20a764486bf/go.mod h1:vxmQPeIQxPf6Jf9rM8R+B4rKBqLA2AjttNxkFBL2Plk= +github.com/lightninglabs/lndclient v0.16.0-10 h1:cMBJNfssBQtpgYIu23QLP/qw0ijiT5SBZffnXz8zjJk= +github.com/lightninglabs/lndclient v0.16.0-10/go.mod h1:mqY0znSNa+M40HZowwKfno29RyZnmxoqo++BlYP82EY= github.com/lightninglabs/neutrino v0.15.0 h1:yr3uz36fLAq8hyM0TRUVlef1TRNoWAqpmmNlVtKUDtI= github.com/lightninglabs/neutrino v0.15.0/go.mod h1:pmjwElN/091TErtSE9Vd5W4hpxoG2/+xlb+HoPm9Gug= github.com/lightninglabs/neutrino/cache v1.1.1 h1:TllWOSlkABhpgbWJfzsrdUaDH2fBy/54VSIB4vVqV8M= github.com/lightninglabs/neutrino/cache v1.1.1/go.mod h1:XJNcgdOw1LQnanGjw8Vj44CvguYA25IMKjWFZczwZuo= -github.com/lightninglabs/pool v0.5.7-alpha.0.20220715160511-f7c1ef26af2b h1:rr0bxgJXScA9WehMv5OxKQrUd4G2EVXBub8my9N+TcA= -github.com/lightninglabs/pool v0.5.7-alpha.0.20220715160511-f7c1ef26af2b/go.mod h1:iQJIB6oPP++G0PfOu9wVNNxuTYV++gDetYyPmz7VUJU= +github.com/lightninglabs/pool v0.6.2-beta.0.20230329135228-c3bffb52df3a h1:beyzoh3UcnQNrFWNNomoueAj3nbuyMjiGQHMqRxspL8= +github.com/lightninglabs/pool v0.6.2-beta.0.20230329135228-c3bffb52df3a/go.mod h1:K84YAySAQak8Jxpno8Gj/7IAl5n8TtdCYH8V+J6VwVM= +github.com/lightninglabs/pool/auctioneerrpc v1.0.7 h1:+a1ynzXAHDlFT4pOeKeAfV38rlwRX3wVdKnGxggtDEQ= +github.com/lightninglabs/pool/auctioneerrpc v1.0.7/go.mod h1:F9uND5Kpj2eYeYe0RLi8IWQHsRjQ88FUp8itkYmX1Mo= github.com/lightningnetwork/lightning-onion v1.2.1-0.20221202012345-ca23184850a1 h1:Wm0g70gkcAu2pGpNZwfWPSVOY21j8IyYsNewwK4OkT4= github.com/lightningnetwork/lightning-onion v1.2.1-0.20221202012345-ca23184850a1/go.mod h1:7dDx73ApjEZA0kcknI799m2O5kkpfg4/gr7N092ojNo= -github.com/lightningnetwork/lnd v0.16.0-beta.rc2 h1:49vVlAzUBcE6kEBDl77ohKsFmlzJ5YOZ4dGHCzNoYb4= -github.com/lightningnetwork/lnd v0.16.0-beta.rc2/go.mod h1:MepaR6bfcPFLt6dGfz3Y8P2BFtvOLJuqT6aulf2eYhE= +github.com/lightningnetwork/lnd v0.16.0-beta h1:h1EIN501DuFrBboO7vPiZ6Z7c9PIOx7IjRw1JhB5hEE= +github.com/lightningnetwork/lnd v0.16.0-beta/go.mod h1:dpm/SAXH7CG00ax3Pks7T2hVvvxdX/cc5ACJNPw+vLo= github.com/lightningnetwork/lnd/clock v1.0.1/go.mod h1:KnQudQ6w0IAMZi1SgvecLZQZ43ra2vpDNj7H/aasemg= github.com/lightningnetwork/lnd/clock v1.1.0 h1:/yfVAwtPmdx45aQBoXQImeY7sOIEr7IXlImRMBOZ7GQ= github.com/lightningnetwork/lnd/clock v1.1.0/go.mod h1:KnQudQ6w0IAMZi1SgvecLZQZ43ra2vpDNj7H/aasemg= @@ -655,8 +660,8 @@ github.com/ulikunitz/xz v0.5.6/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4A github.com/ulikunitz/xz v0.5.7/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= github.com/ulikunitz/xz v0.5.10 h1:t92gobL9l3HE202wg3rlk19F6X+JOxl9BBrCCMYEYd8= github.com/ulikunitz/xz v0.5.10/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= +github.com/xdg-go/stringprep v1.0.2 h1:6iq84/ryjjeRmMJwxutI51F2GIPlP5BfTvXHeYjyhBc= github.com/xdg-go/stringprep v1.0.2/go.mod h1:8F9zXuvzgwmyT5DUm4GUfZGDdT3W+LCvS6+da4O5kxM= -github.com/xdg-go/stringprep v1.0.3 h1:kdwGpVNwPFtjs98xCGkHjQtGKh86rDcRZN17QEMCOIs= github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 h1:nIPpBwaJSVYIxUFsDv3M8ofmx9yWTog9BfvIu0q41lo= github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8/go.mod h1:HUYIGzjTL3rfEspMxjDjgmT5uz5wzYJKVo23qUhYTos= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 h1:eY9dn8+vbi4tKz5Qo6v2eYzo7kUS51QINcR5jNpbZS8= @@ -975,6 +980,7 @@ golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.2.0 h1:G6AHpWxTMGY1KyEYoAQ5WTtIekUUvDNjan3ugu60JvE= golang.org/x/tools v0.2.0/go.mod h1:y4OqIKeOV/fWJetJ8bXPU1sEVniLMIyDAZWeHdV+NTA= diff --git a/lnd/signer.go b/lnd/signer.go index ffb62b9..6385918 100644 --- a/lnd/signer.go +++ b/lnd/signer.go @@ -6,6 +6,7 @@ import ( "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcec/v2/ecdsa" + "github.com/btcsuite/btcd/btcec/v2/schnorr" "github.com/btcsuite/btcd/btcutil/hdkeychain" "github.com/btcsuite/btcd/btcutil/psbt" "github.com/btcsuite/btcd/chaincfg" @@ -35,9 +36,65 @@ func (s *Signer) SignOutputRaw(tx *wire.MsgTx, } privKey = maybeTweakPrivKey(signDesc, privKey) + + sigHashes := txscript.NewTxSigHashes(tx, signDesc.PrevOutputFetcher) + if txscript.IsPayToTaproot(signDesc.Output.PkScript) { + // Are we spending a script path or the key path? The API is + // slightly different, so we need to account for that to get the + // raw signature. + var rawSig []byte + switch signDesc.SignMethod { + case input.TaprootKeySpendBIP0086SignMethod, + input.TaprootKeySpendSignMethod: + + // This function tweaks the private key using the tap + // root key supplied as the tweak. + rawSig, err = txscript.RawTxInTaprootSignature( + tx, sigHashes, signDesc.InputIndex, + signDesc.Output.Value, signDesc.Output.PkScript, + signDesc.TapTweak, signDesc.HashType, + privKey, + ) + if err != nil { + return nil, err + } + + case input.TaprootScriptSpendSignMethod: + leaf := txscript.TapLeaf{ + LeafVersion: txscript.BaseLeafVersion, + Script: witnessScript, + } + rawSig, err = txscript.RawTxInTapscriptSignature( + tx, sigHashes, signDesc.InputIndex, + signDesc.Output.Value, signDesc.Output.PkScript, + leaf, signDesc.HashType, privKey, + ) + if err != nil { + return nil, err + } + + default: + return nil, fmt.Errorf("unknown sign method: %v", + signDesc.SignMethod) + } + + // The signature returned above might have a sighash flag + // attached if a non-default type was used. We'll slice this + // off if it exists to ensure we can properly parse the raw + // signature. + sig, err := schnorr.ParseSignature( + rawSig[:schnorr.SignatureSize], + ) + if err != nil { + return nil, err + } + + return sig, nil + } + amt := signDesc.Output.Value sig, err := txscript.RawTxInWitnessSignature( - tx, signDesc.SigHashes, signDesc.InputIndex, amt, + tx, sigHashes, signDesc.InputIndex, amt, witnessScript, signDesc.HashType, privKey, ) if err != nil {