Skip to content

Commit

Permalink
Merge pull request lightninglabs#380 from lightninglabs/taproot
Browse files Browse the repository at this point in the history
Upgrade master account to Taproot
  • Loading branch information
Roasbeef authored Jul 28, 2022
2 parents ccbc2d3 + 4779a17 commit 468ab0e
Show file tree
Hide file tree
Showing 38 changed files with 1,257 additions and 891 deletions.
80 changes: 63 additions & 17 deletions account/auctioneer.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,33 @@ import (
"github.com/lightningnetwork/lnd/lnwallet"
)

// AuctioneerVersion represents the version of the auctioneer account.
type AuctioneerVersion uint8

const (
// VersionInitialNoVersion is the initial version any legacy account has
// that technically wasn't versioned at all.
VersionInitialNoVersion AuctioneerVersion = 0

// VersionTaprootEnabled is the version that introduced account
// versioning and the upgrade to Taproot (with MuSig2 multi-sig).
VersionTaprootEnabled AuctioneerVersion = 1
)

// String returns the string representation of the version.
func (v AuctioneerVersion) String() string {
switch v {
case VersionInitialNoVersion:
return "account_p2wsh"

case VersionTaprootEnabled:
return "account_p2tr"

default:
return fmt.Sprintf("unknown <%d>", v)
}
}

// Auctioneer is the master auctioneer account, this will be threaded along in
// the batch along side all the other accounts. We'll use this account to
// accrue all the execution fees we earn each batch.
Expand All @@ -38,28 +65,34 @@ type Auctioneer struct {
// IsPending determines whether the account is pending its confirmation
// in the chain.
IsPending bool

// Version is the version of the auctioneer account.
Version AuctioneerVersion
}

// AccountWitnessScript computes the raw witness script of the target account.
func (a *Auctioneer) AccountWitnessScript() ([]byte, error) {
// accountV0WitnessScript computes the raw witness script of the target account.
func (a *Auctioneer) accountV0WitnessScript() ([]byte, error) {
batchKey, err := btcec.ParsePubKey(a.BatchKey[:])
if err != nil {
return nil, err
}

return AuctioneerWitnessScript(
return AuctioneerV0WitnessScript(
batchKey, a.AuctioneerKey.PubKey,
)
}

// Output returns the output of auctioneer's output as it should appear on the
// last batch transaction.
func (a *Auctioneer) Output() (*wire.TxOut, error) {
witnessScript, err := a.AccountWitnessScript()
batchKey, err := btcec.ParsePubKey(a.BatchKey[:])
if err != nil {
return nil, err
}
pkScript, err := input.WitnessScriptHash(witnessScript)

pkScript, err := AuctioneerAccountScript(
a.Version, batchKey, a.AuctioneerKey.PubKey,
)
if err != nil {
return nil, err
}
Expand All @@ -73,16 +106,12 @@ func (a *Auctioneer) Output() (*wire.TxOut, error) {
// AccountWitness attempts to construct a fully valid witness which can be used
// to spend the auctioneer's account on the batch execution transaction.
func (a *Auctioneer) AccountWitness(signer lndclient.SignerClient,
tx *wire.MsgTx, inputIndex int,
tx *wire.MsgTx, inputIndex int, version AuctioneerVersion,
prevOutputs []*wire.TxOut) (wire.TxWitness, error) {

// First, we'll compute the witness script, and its corresponding
// witness program as we'll need both for the sign descriptor below.
witnessScript, err := a.AccountWitnessScript()
if err != nil {
return nil, err
}
pkScript, err := input.WitnessScriptHash(witnessScript)
witnessScript, err := a.accountV0WitnessScript()
if err != nil {
return nil, err
}
Expand All @@ -96,9 +125,13 @@ func (a *Auctioneer) AccountWitness(signer lndclient.SignerClient,
if err != nil {
return nil, err
}
batchTweak := input.SingleTweakBytes(
batchKey, a.AuctioneerKey.PubKey,
batchTweak := input.SingleTweakBytes(batchKey, a.AuctioneerKey.PubKey)
pkScript, err := AuctioneerAccountScript(
version, batchKey, a.AuctioneerKey.PubKey,
)
if err != nil {
return nil, err
}

// Now that we have all the required items, we'll query the Signer for
// a valid signature for our account output.
Expand All @@ -117,6 +150,14 @@ func (a *Auctioneer) AccountWitness(signer lndclient.SignerClient,
HashType: txscript.SigHashAll,
InputIndex: inputIndex,
}

// Signing for a Taproot input works somewhat differently.
if version == VersionTaprootEnabled {
signDesc.WitnessScript = nil
signDesc.HashType = txscript.SigHashDefault
signDesc.SignMethod = input.TaprootKeySpendBIP0086SignMethod
}

ctx := context.Background()
sigs, err := signer.SignOutputRaw(
ctx, tx, []*lndclient.SignDescriptor{signDesc}, prevOutputs,
Expand All @@ -127,18 +168,23 @@ func (a *Auctioneer) AccountWitness(signer lndclient.SignerClient,

// Next we'll construct the account witness given our witness script,
// pubkey, and signature.
witness := AuctioneerWitness(witnessScript, sigs[0])
witness := AuctioneerWitness(version, witnessScript, sigs[0])

txCopy := tx.Copy()
txCopy.TxIn[inputIndex].Witness = witness

// As a final step, we'll ensure the signature we just generated above
// is valid.
prevOutputFetcher := txscript.NewMultiPrevOutFetcher(nil)
for idx := range prevOutputs {
prevOutputFetcher.AddPrevOut(
txCopy.TxIn[idx].PreviousOutPoint, prevOutputs[idx],
)
}
sigHashes := txscript.NewTxSigHashes(txCopy, prevOutputFetcher)
vm, err := txscript.NewEngine(
pkScript, txCopy, inputIndex, txscript.StandardVerifyFlags,
nil, nil, int64(a.Balance), txscript.NewCannedPrevOutputFetcher(
pkScript, int64(a.Balance),
),
nil, sigHashes, int64(a.Balance), prevOutputFetcher,
)
if err != nil {
return nil, err
Expand Down
91 changes: 44 additions & 47 deletions account/auctioneer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,61 +21,58 @@ func TestAuctioneerAccountWitness(t *testing.T) {
// First we'll generate the auctioneer key and a random batch key that
// we'll use for this purpose.
batchKey, err := btcec.NewPrivateKey()
if err != nil {
t.Fatalf("unable to make batch key: %v", err)
}
require.NoError(t, err)
auctioneerKey, err := btcec.NewPrivateKey()
if err != nil {
t.Fatalf("unable to make auctioneer key: %v", err)
}
require.NoError(t, err)

// With these two keys generated, we can now make the full auctioneer
// account.
acct := &Auctioneer{
Balance: 1_000_000,
AuctioneerKey: &keychain.KeyDescriptor{
PubKey: auctioneerKey.PubKey(),
},
}
copy(acct.BatchKey[:], batchKey.PubKey().SerializeCompressed())
runTest := func(masterAcctVersion AuctioneerVersion) {
// With these two keys generated, we can now make the full
// auctioneer account.
acct := &Auctioneer{
Balance: 1_000_000,
AuctioneerKey: &keychain.KeyDescriptor{
PubKey: auctioneerKey.PubKey(),
},
Version: masterAcctVersion,
}
copy(acct.BatchKey[:], batchKey.PubKey().SerializeCompressed())

acctOutput, err := acct.Output()
if err != nil {
t.Fatalf("unable to create acct output: %v", err)
}
acctOutput, err := acct.Output()
require.NoError(t, err)

// We'll construct a sweep transaction that just sweeps the output back
// into an identical one.
spendTx := wire.NewMsgTx(2)
spendTx.AddTxIn(&wire.TxIn{})
spendTx.AddTxOut(acctOutput)
// We'll construct a sweep transaction that just sweeps the
// output back into an identical one.
spendTx := wire.NewMsgTx(2)
spendTx.AddTxIn(&wire.TxIn{})
spendTx.AddTxOut(acctOutput)

// Now we'll construct the witness to simulate a spend of the account.
signer := &MockSigner{
[]*btcec.PrivateKey{auctioneerKey},
}
witness, err := acct.AccountWitness(
signer, spendTx, 0, []*wire.TxOut{acctOutput},
)
if err != nil {
t.Fatalf("unable to generate witness: %v", err)
}
spendTx.TxIn[0].Witness = witness
// Now we'll construct the witness to simulate a spend of the
// account.
signer := &MockSigner{
[]*btcec.PrivateKey{auctioneerKey},
}
witness, err := acct.AccountWitness(
signer, spendTx, 0, masterAcctVersion,
[]*wire.TxOut{acctOutput},
)
require.NoError(t, err)
spendTx.TxIn[0].Witness = witness

// Ensure that the witness script we generate is valid.
vm, err := txscript.NewEngine(
acctOutput.PkScript, spendTx, 0, txscript.StandardVerifyFlags,
nil, nil, acctOutput.Value,
txscript.NewCannedPrevOutputFetcher(
// Ensure that the witness script we generate is valid.
prevOutFetcher := txscript.NewCannedPrevOutputFetcher(
acctOutput.PkScript, acctOutput.Value,
),
)
if err != nil {
t.Fatalf("unable to create engine: %v", err)
}
if err := vm.Execute(); err != nil {
t.Fatalf("invalid spend: %v", err)
)
sigHashes := txscript.NewTxSigHashes(spendTx, prevOutFetcher)
vm, err := txscript.NewEngine(
acctOutput.PkScript, spendTx, 0,
txscript.StandardVerifyFlags, nil, sigHashes,
acctOutput.Value, prevOutFetcher,
)
require.NoError(t, err)
require.NoError(t, vm.Execute())
}
runTest(VersionInitialNoVersion)
runTest(VersionTaprootEnabled)
}

// TestAuctioneerInputWitness tests that we are able to properly produce valid
Expand Down
31 changes: 31 additions & 0 deletions account/mock.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/chaincfg"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcd/wire"
"github.com/lightninglabs/aperture/lsat"
"github.com/lightninglabs/lndclient"
Expand Down Expand Up @@ -319,6 +320,36 @@ func (m *MockSigner) SignOutputRaw(ctx context.Context, tx *wire.MsgTx,
HashType: signDescriptors[0].HashType,
SigHashes: input.NewTxSigHashesV0Only(tx),
InputIndex: signDescriptors[0].InputIndex,
SignMethod: signDescriptors[0].SignMethod,
}

if lndSignDescriptor.SignMethod == input.TaprootKeySpendBIP0086SignMethod {
prevOutputFetcher := txscript.NewMultiPrevOutFetcher(nil)
for idx := range tx.TxIn {
prevOutputFetcher.AddPrevOut(
tx.TxIn[idx].PreviousOutPoint, prevOutputs[idx],
)
}
sigHashes := txscript.NewTxSigHashes(tx, prevOutputFetcher)

privKey := m.PrivKeys[0]
if len(lndSignDescriptor.SingleTweak) > 0 {
privKey = input.TweakPrivKey(
privKey, lndSignDescriptor.SingleTweak,
)
}

witnessScript, err := txscript.TaprootWitnessSignature(
tx, sigHashes, lndSignDescriptor.InputIndex,
lndSignDescriptor.Output.Value,
lndSignDescriptor.Output.PkScript,
txscript.SigHashDefault, privKey,
)
if err != nil {
return nil, err
}

return witnessScript, nil
}

sig, err := s.SignOutputRaw(tx, lndSignDescriptor)
Expand Down
Loading

0 comments on commit 468ab0e

Please sign in to comment.