mirror of
https://github.com/lightningnetwork/lnd.git
synced 2025-03-04 01:36:24 +01:00
watchtower/lookout: make justice desciptor taproot ready
This commit is contained in:
parent
5960253357
commit
c50aa10194
3 changed files with 264 additions and 51 deletions
|
@ -176,6 +176,8 @@ func (p *JusticeDescriptor) assembleJusticeTxn(txWeight int64,
|
|||
return nil, fmt.Errorf("error creating previous output "+
|
||||
"fetcher: %v", err)
|
||||
}
|
||||
|
||||
hashes := txscript.NewTxSigHashes(justiceTxn, prevOutFetcher)
|
||||
for _, inp := range inputs {
|
||||
// Lookup the input's new post-sort position.
|
||||
i := inputIndex[inp.outPoint]
|
||||
|
@ -186,7 +188,7 @@ func (p *JusticeDescriptor) assembleJusticeTxn(txWeight int64,
|
|||
vm, err := txscript.NewEngine(
|
||||
inp.txOut.PkScript, justiceTxn, i,
|
||||
txscript.StandardVerifyFlags,
|
||||
nil, nil, inp.txOut.Value, prevOutFetcher,
|
||||
nil, hashes, inp.txOut.Value, prevOutFetcher,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
|
|
@ -10,6 +10,7 @@ import (
|
|||
"github.com/btcsuite/btcd/btcutil/txsort"
|
||||
"github.com/btcsuite/btcd/txscript"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
secp "github.com/decred/dcrd/dcrec/secp256k1/v4"
|
||||
"github.com/lightningnetwork/lnd/input"
|
||||
"github.com/lightningnetwork/lnd/keychain"
|
||||
"github.com/lightningnetwork/lnd/lnwallet"
|
||||
|
@ -53,6 +54,8 @@ var (
|
|||
altruistCommitType = blob.FlagCommitOutputs.Type()
|
||||
|
||||
altruistAnchorCommitType = blob.TypeAltruistAnchorCommit
|
||||
|
||||
altruisticTaprootCommitType = blob.TypeAltruistTaprootCommit
|
||||
)
|
||||
|
||||
// TestJusticeDescriptor asserts that a JusticeDescriptor is able to produce the
|
||||
|
@ -74,6 +77,10 @@ func TestJusticeDescriptor(t *testing.T) {
|
|||
name: "altruist anchor commit type",
|
||||
blobType: altruistAnchorCommitType,
|
||||
},
|
||||
{
|
||||
name: "altruist taproot commit type",
|
||||
blobType: altruisticTaprootCommitType,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
|
@ -85,6 +92,7 @@ func TestJusticeDescriptor(t *testing.T) {
|
|||
|
||||
func testJusticeDescriptor(t *testing.T, blobType blob.Type) {
|
||||
isAnchorChannel := blobType.IsAnchorChannel()
|
||||
isTaprootChannel := blobType.IsTaprootChannel()
|
||||
|
||||
const (
|
||||
localAmount = btcutil.Amount(100000)
|
||||
|
@ -108,15 +116,34 @@ func testJusticeDescriptor(t *testing.T, blobType blob.Type) {
|
|||
toRemoteKeyLoc = signer.AddPrivKey(toRemoteSK)
|
||||
)
|
||||
|
||||
// Construct the to-local witness script.
|
||||
toLocalScript, err := input.CommitScriptToSelf(
|
||||
csvDelay, toLocalPK, revPK,
|
||||
var (
|
||||
toLocalScript, toLocalScriptHash []byte
|
||||
toLocalCommitTree *input.CommitScriptTree
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Compute the to-local witness script hash.
|
||||
toLocalScriptHash, err := input.WitnessScriptHash(toLocalScript)
|
||||
require.NoError(t, err)
|
||||
if isTaprootChannel {
|
||||
toLocalCommitTree, err = input.NewLocalCommitScriptTree(
|
||||
csvDelay, toLocalPK, revPK,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
toLocalScript = toLocalCommitTree.RevocationLeaf.Script
|
||||
|
||||
toLocalScriptHash, err = input.PayToTaprootScript(
|
||||
toLocalCommitTree.TaprootKey,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
} else {
|
||||
// Construct the to-local witness script.
|
||||
toLocalScript, err = input.CommitScriptToSelf(
|
||||
csvDelay, toLocalPK, revPK,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Compute the to-local witness script hash.
|
||||
toLocalScriptHash, err = input.WitnessScriptHash(toLocalScript)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
// Compute the to-remote redeem script, witness script hash, and
|
||||
// sequence numbers.
|
||||
|
@ -140,8 +167,37 @@ func testJusticeDescriptor(t *testing.T, blobType blob.Type) {
|
|||
toRemoteRedeemScript []byte
|
||||
toRemoteScriptHash []byte
|
||||
toRemoteSigningScript []byte
|
||||
toRemoteCtrlBlock []byte
|
||||
)
|
||||
if isAnchorChannel {
|
||||
switch {
|
||||
case isTaprootChannel:
|
||||
toRemoteSequence = 1
|
||||
|
||||
commitScriptTree, err := input.NewRemoteCommitScriptTree(
|
||||
toRemotePK,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
toRemoteSigningScript = commitScriptTree.SettleLeaf.Script
|
||||
|
||||
toRemoteScriptHash, err = input.PayToTaprootScript(
|
||||
commitScriptTree.TaprootKey,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
tree := commitScriptTree.TapscriptTree
|
||||
settleTapleafHash := commitScriptTree.SettleLeaf.TapHash()
|
||||
settleIdx := tree.LeafProofIndex[settleTapleafHash]
|
||||
settleMerkleProof := tree.LeafMerkleProofs[settleIdx]
|
||||
settleControlBlock := settleMerkleProof.ToControlBlock(
|
||||
&input.TaprootNUMSKey,
|
||||
)
|
||||
|
||||
ctrlBytes, err := settleControlBlock.ToBytes()
|
||||
require.NoError(t, err)
|
||||
toRemoteCtrlBlock = ctrlBytes
|
||||
|
||||
case isAnchorChannel:
|
||||
toRemoteSequence = 1
|
||||
toRemoteRedeemScript, err = input.CommitScriptToRemoteConfirmed(
|
||||
toRemotePK,
|
||||
|
@ -155,7 +211,8 @@ func testJusticeDescriptor(t *testing.T, blobType blob.Type) {
|
|||
|
||||
// As it should be.
|
||||
toRemoteSigningScript = toRemoteRedeemScript
|
||||
} else {
|
||||
|
||||
default:
|
||||
toRemoteRedeemScript = toRemotePK.SerializeCompressed()
|
||||
toRemoteScriptHash, err = input.CommitScriptUnencumbered(
|
||||
toRemotePK,
|
||||
|
@ -269,31 +326,82 @@ func testJusticeDescriptor(t *testing.T, blobType blob.Type) {
|
|||
justiceTxn.TxOut = outputs
|
||||
txsort.InPlaceSort(justiceTxn)
|
||||
|
||||
hashCache := input.NewTxSigHashesV0Only(justiceTxn)
|
||||
var (
|
||||
toLocalSignDesc *input.SignDescriptor
|
||||
toRemoteSignDesc *input.SignDescriptor
|
||||
)
|
||||
|
||||
// Create the sign descriptor used to sign for the to-local input.
|
||||
toLocalSignDesc := &input.SignDescriptor{
|
||||
KeyDesc: keychain.KeyDescriptor{
|
||||
KeyLocator: revKeyLoc,
|
||||
},
|
||||
WitnessScript: toLocalScript,
|
||||
Output: breachTxn.TxOut[0],
|
||||
SigHashes: hashCache,
|
||||
InputIndex: 0,
|
||||
HashType: txscript.SigHashAll,
|
||||
}
|
||||
if isTaprootChannel {
|
||||
prevOuts := map[wire.OutPoint]*wire.TxOut{
|
||||
{
|
||||
Hash: breachTxID,
|
||||
Index: 0,
|
||||
}: breachTxn.TxOut[0],
|
||||
{
|
||||
Hash: breachTxID,
|
||||
Index: 1,
|
||||
}: breachTxn.TxOut[1],
|
||||
}
|
||||
prevOutputFetcher := txscript.NewMultiPrevOutFetcher(prevOuts)
|
||||
hashCache := txscript.NewTxSigHashes(
|
||||
justiceTxn, prevOutputFetcher,
|
||||
)
|
||||
|
||||
// Create the sign descriptor used to sign for the to-remote input.
|
||||
toRemoteSignDesc := &input.SignDescriptor{
|
||||
KeyDesc: keychain.KeyDescriptor{
|
||||
KeyLocator: toRemoteKeyLoc,
|
||||
PubKey: toRemotePK,
|
||||
},
|
||||
WitnessScript: toRemoteSigningScript,
|
||||
Output: breachTxn.TxOut[1],
|
||||
SigHashes: hashCache,
|
||||
InputIndex: 1,
|
||||
HashType: txscript.SigHashAll,
|
||||
toLocalSignDesc = &input.SignDescriptor{
|
||||
KeyDesc: keychain.KeyDescriptor{
|
||||
KeyLocator: revKeyLoc,
|
||||
},
|
||||
WitnessScript: toLocalScript,
|
||||
Output: breachTxn.TxOut[0],
|
||||
SigHashes: hashCache,
|
||||
PrevOutputFetcher: prevOutputFetcher,
|
||||
InputIndex: 0,
|
||||
HashType: txscript.SigHashDefault,
|
||||
SignMethod: input.TaprootScriptSpendSignMethod,
|
||||
}
|
||||
|
||||
toRemoteSignDesc = &input.SignDescriptor{
|
||||
KeyDesc: keychain.KeyDescriptor{
|
||||
KeyLocator: toRemoteKeyLoc,
|
||||
PubKey: toRemotePK,
|
||||
},
|
||||
WitnessScript: toRemoteSigningScript,
|
||||
Output: breachTxn.TxOut[1],
|
||||
PrevOutputFetcher: prevOutputFetcher,
|
||||
SigHashes: hashCache,
|
||||
InputIndex: 1,
|
||||
HashType: txscript.SigHashDefault,
|
||||
SignMethod: input.TaprootScriptSpendSignMethod,
|
||||
}
|
||||
} else {
|
||||
hashCache := input.NewTxSigHashesV0Only(justiceTxn)
|
||||
|
||||
// Create the sign descriptor used to sign for the to-local
|
||||
// input.
|
||||
toLocalSignDesc = &input.SignDescriptor{
|
||||
KeyDesc: keychain.KeyDescriptor{
|
||||
KeyLocator: revKeyLoc,
|
||||
},
|
||||
WitnessScript: toLocalScript,
|
||||
Output: breachTxn.TxOut[0],
|
||||
SigHashes: hashCache,
|
||||
InputIndex: 0,
|
||||
HashType: txscript.SigHashAll,
|
||||
}
|
||||
|
||||
// Create the sign descriptor used to sign for the to-remote
|
||||
// input.
|
||||
toRemoteSignDesc = &input.SignDescriptor{
|
||||
KeyDesc: keychain.KeyDescriptor{
|
||||
KeyLocator: toRemoteKeyLoc,
|
||||
PubKey: toRemotePK,
|
||||
},
|
||||
WitnessScript: toRemoteSigningScript,
|
||||
Output: breachTxn.TxOut[1],
|
||||
SigHashes: hashCache,
|
||||
InputIndex: 1,
|
||||
HashType: txscript.SigHashAll,
|
||||
}
|
||||
}
|
||||
|
||||
// Verify that our test justice transaction is sane.
|
||||
|
@ -301,21 +409,19 @@ func testJusticeDescriptor(t *testing.T, blobType blob.Type) {
|
|||
err = blockchain.CheckTransactionSanity(btx)
|
||||
require.Nil(t, err)
|
||||
|
||||
// Compute a DER-encoded signature for the to-local input.
|
||||
// Compute a signature for the to-local input.
|
||||
toLocalSigRaw, err := signer.SignOutputRaw(justiceTxn, toLocalSignDesc)
|
||||
require.Nil(t, err)
|
||||
|
||||
// Compute the witness for the to-remote input. The first element is a
|
||||
// DER-encoded signature under the to-remote pubkey. The sighash flag is
|
||||
// also present, so we trim it.
|
||||
// Compute the witness for the to-remote input.
|
||||
toRemoteSigRaw, err := signer.SignOutputRaw(justiceTxn, toRemoteSignDesc)
|
||||
require.Nil(t, err)
|
||||
|
||||
// Convert the DER to-local sig into a fixed-size signature.
|
||||
// Convert the to-local sig into a fixed-size signature.
|
||||
toLocalSig, err := lnwire.NewSigFromSignature(toLocalSigRaw)
|
||||
require.Nil(t, err)
|
||||
|
||||
// Convert the DER to-remote sig into a fixed-size signature.
|
||||
// Convert the to-remote sig into a fixed-size signature.
|
||||
toRemoteSig, err := lnwire.NewSigFromSignature(toRemoteSigRaw)
|
||||
require.Nil(t, err)
|
||||
|
||||
|
@ -342,7 +448,7 @@ func testJusticeDescriptor(t *testing.T, blobType blob.Type) {
|
|||
// Exact retribution on the offender. If no error is returned, we expect
|
||||
// the justice transaction to be published via the channel.
|
||||
err = punisher.Punish(justiceDesc, nil)
|
||||
require.Nil(t, err)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Retrieve the published justice transaction.
|
||||
var wtJusticeTxn *wire.MsgTx
|
||||
|
@ -352,18 +458,59 @@ func testJusticeDescriptor(t *testing.T, blobType blob.Type) {
|
|||
t.Fatalf("punisher did not publish justice txn")
|
||||
}
|
||||
|
||||
// Construct the test's to-local witness.
|
||||
justiceTxn.TxIn[0].Witness = make([][]byte, 3)
|
||||
justiceTxn.TxIn[0].Witness[0] = append(toLocalSigRaw.Serialize(),
|
||||
byte(txscript.SigHashAll))
|
||||
justiceTxn.TxIn[0].Witness[1] = []byte{1}
|
||||
justiceTxn.TxIn[0].Witness[2] = toLocalScript
|
||||
if isTaprootChannel {
|
||||
revokeLeaf := txscript.NewBaseTapLeaf(toLocalScript)
|
||||
outputKey := txscript.ComputeTaprootOutputKey(
|
||||
&input.TaprootNUMSKey, toLocalCommitTree.TapscriptRoot,
|
||||
)
|
||||
|
||||
// Construct the test's to-remote witness.
|
||||
justiceTxn.TxIn[1].Witness = make([][]byte, 2)
|
||||
justiceTxn.TxIn[1].Witness[0] = append(toRemoteSigRaw.Serialize(),
|
||||
byte(txscript.SigHashAll))
|
||||
justiceTxn.TxIn[1].Witness[1] = toRemoteRedeemScript
|
||||
var outputKeyYIsOdd bool
|
||||
if outputKey.SerializeCompressed()[0] ==
|
||||
secp.PubKeyFormatCompressedOdd {
|
||||
|
||||
outputKeyYIsOdd = true
|
||||
}
|
||||
|
||||
delayScriptHash := toLocalCommitTree.SettleLeaf.TapHash()
|
||||
|
||||
ctrlBlock := txscript.ControlBlock{
|
||||
InternalKey: &input.TaprootNUMSKey,
|
||||
OutputKeyYIsOdd: outputKeyYIsOdd,
|
||||
LeafVersion: revokeLeaf.LeafVersion,
|
||||
InclusionProof: delayScriptHash[:],
|
||||
}
|
||||
|
||||
ctrlBytes, err := ctrlBlock.ToBytes()
|
||||
require.NoError(t, err)
|
||||
|
||||
justiceTxn.TxIn[0].Witness = make([][]byte, 3)
|
||||
justiceTxn.TxIn[0].Witness[0] = toLocalSigRaw.Serialize()
|
||||
justiceTxn.TxIn[0].Witness[1] = toLocalScript
|
||||
justiceTxn.TxIn[0].Witness[2] = ctrlBytes
|
||||
|
||||
// Construct the test's to-remote witness.
|
||||
justiceTxn.TxIn[1].Witness = make([][]byte, 3)
|
||||
justiceTxn.TxIn[1].Witness[0] = toRemoteSigRaw.Serialize()
|
||||
justiceTxn.TxIn[1].Witness[1] = toRemoteSigningScript
|
||||
justiceTxn.TxIn[1].Witness[2] = toRemoteCtrlBlock
|
||||
} else {
|
||||
// Construct the test's to-local witness.
|
||||
justiceTxn.TxIn[0].Witness = make([][]byte, 3)
|
||||
justiceTxn.TxIn[0].Witness[0] = append(
|
||||
toLocalSigRaw.Serialize(),
|
||||
byte(txscript.SigHashAll),
|
||||
)
|
||||
justiceTxn.TxIn[0].Witness[1] = []byte{1}
|
||||
justiceTxn.TxIn[0].Witness[2] = toLocalScript
|
||||
|
||||
// Construct the test's to-remote witness.
|
||||
justiceTxn.TxIn[1].Witness = make([][]byte, 2)
|
||||
justiceTxn.TxIn[1].Witness[0] = append(
|
||||
toRemoteSigRaw.Serialize(),
|
||||
byte(txscript.SigHashAll),
|
||||
)
|
||||
justiceTxn.TxIn[1].Witness[1] = toRemoteRedeemScript
|
||||
}
|
||||
|
||||
// Assert that the watchtower derives the same justice txn.
|
||||
require.Equal(t, justiceTxn, wtJusticeTxn)
|
||||
|
|
|
@ -2,6 +2,7 @@ package wtmock
|
|||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"github.com/btcsuite/btcd/btcec/v2"
|
||||
|
@ -47,6 +48,69 @@ func (s *MockSigner) SignOutputRaw(tx *wire.MsgTx,
|
|||
panic("cannot sign w/ unknown key")
|
||||
}
|
||||
|
||||
// In case of a taproot output any signature is always a Schnorr
|
||||
// signature, based on the new tapscript sighash algorithm.
|
||||
if txscript.IsPayToTaproot(signDesc.Output.PkScript) {
|
||||
sigHashes := txscript.NewTxSigHashes(
|
||||
tx, signDesc.PrevOutputFetcher,
|
||||
)
|
||||
|
||||
// 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
|
||||
err error
|
||||
)
|
||||
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
|
||||
}
|
||||
|
||||
sig, err := txscript.RawTxInWitnessSignature(
|
||||
tx, signDesc.SigHashes, signDesc.InputIndex, amt,
|
||||
witnessScript, signDesc.HashType, privKey,
|
||||
|
|
Loading…
Add table
Reference in a new issue