diff --git a/watchtower/lookout/justice_descriptor.go b/watchtower/lookout/justice_descriptor.go index cea3ae03..e86748e2 100644 --- a/watchtower/lookout/justice_descriptor.go +++ b/watchtower/lookout/justice_descriptor.go @@ -48,6 +48,7 @@ type breachedInput struct { txOut *wire.TxOut outPoint wire.OutPoint witness [][]byte + sequence uint32 } // commitToLocalInput extracts the information required to spend the commit @@ -104,20 +105,35 @@ func (p *JusticeDescriptor) commitToRemoteInput() (*breachedInput, error) { return nil, err } - // Since the to-remote witness script should just be a regular p2wkh - // output, we'll parse it to retrieve the public key. - toRemotePubKey, err := btcec.ParsePubKey(toRemoteScript, btcec.S256()) - if err != nil { - return nil, err - } - - // Compute the witness script hash from the to-remote pubkey, which will - // be used to locate the input on the breach commitment transaction. - toRemoteScriptHash, err := input.CommitScriptUnencumbered( - toRemotePubKey, + var ( + toRemoteScriptHash []byte + toRemoteSequence uint32 ) - if err != nil { - return nil, err + if p.JusticeKit.BlobType.IsAnchorChannel() { + toRemoteScriptHash, err = input.WitnessScriptHash( + toRemoteScript, + ) + if err != nil { + return nil, err + } + + toRemoteSequence = 1 + } else { + // Since the to-remote witness script should just be a regular p2wkh + // output, we'll parse it to retrieve the public key. + toRemotePubKey, err := btcec.ParsePubKey(toRemoteScript, btcec.S256()) + if err != nil { + return nil, err + } + + // Compute the witness script hash from the to-remote pubkey, which will + // be used to locate the input on the breach commitment transaction. + toRemoteScriptHash, err = input.CommitScriptUnencumbered( + toRemotePubKey, + ) + if err != nil { + return nil, err + } } // Locate the to-remote output on the breaching commitment transaction. @@ -146,6 +162,7 @@ func (p *JusticeDescriptor) commitToRemoteInput() (*breachedInput, error) { txOut: toRemoteTxOut, outPoint: toRemoteOutPoint, witness: buildWitness(witnessStack, toRemoteScript), + sequence: toRemoteSequence, }, nil } @@ -164,6 +181,7 @@ func (p *JusticeDescriptor) assembleJusticeTxn(txWeight int64, totalAmt += btcutil.Amount(input.txOut.Value) justiceTxn.AddTxIn(&wire.TxIn{ PreviousOutPoint: input.outPoint, + Sequence: input.sequence, }) } @@ -279,8 +297,13 @@ func (p *JusticeDescriptor) CreateJusticeTxn() (*wire.MsgTx, error) { if err != nil { return nil, err } - weightEstimate.AddWitnessInput(input.P2WKHWitnessSize) sweepInputs = append(sweepInputs, toRemoteInput) + + if p.JusticeKit.BlobType.IsAnchorChannel() { + weightEstimate.AddWitnessInput(input.ToRemoteConfirmedWitnessSize) + } else { + weightEstimate.AddWitnessInput(input.P2WKHWitnessSize) + } } // TODO(conner): sweep htlc outputs diff --git a/watchtower/lookout/justice_descriptor_test.go b/watchtower/lookout/justice_descriptor_test.go index 93d9c94c..88ef40ae 100644 --- a/watchtower/lookout/justice_descriptor_test.go +++ b/watchtower/lookout/justice_descriptor_test.go @@ -50,6 +50,8 @@ var ( ) altruistCommitType = blob.FlagCommitOutputs.Type() + + altruistAnchorCommitType = blob.TypeAltruistAnchorCommit ) // TestJusticeDescriptor asserts that a JusticeDescriptor is able to produce the @@ -67,6 +69,10 @@ func TestJusticeDescriptor(t *testing.T) { name: "altruist and commit type", blobType: altruistCommitType, }, + { + name: "altruist anchor commit type", + blobType: altruistAnchorCommitType, + }, } for _, test := range tests { @@ -77,6 +83,8 @@ func TestJusticeDescriptor(t *testing.T) { } func testJusticeDescriptor(t *testing.T, blobType blob.Type) { + isAnchorChannel := blobType.IsAnchorChannel() + const ( localAmount = btcutil.Amount(100000) remoteAmount = btcutil.Amount(200000) @@ -111,9 +119,54 @@ func testJusticeDescriptor(t *testing.T, blobType blob.Type) { toLocalScriptHash, err := input.WitnessScriptHash(toLocalScript) require.Nil(t, err) - // Compute the to-remote witness script hash. - toRemoteScriptHash, err := input.CommitScriptUnencumbered(toRemotePK) - require.Nil(t, err) + // Compute the to-remote redeem script, witness script hash, and + // sequence numbers. + // + // NOTE: This is pretty subtle. + // + // The actual redeem script for a p2wkh output is just the pubkey, but + // the witness sighash calculation injects the classic p2kh script: + // OP_DUP OP_HASH160 OP_EQUALVERIFY OP_CHECKSIG. When + // signing for p2wkh we don't pass the raw pubkey as the witness script + // to the sign descriptor (since that's also not a valid script). + // Instead we give it the _pkscript_ of the form OP_0 + // from which pubkey-hash160 is extracted during sighash calculation. + // + // On the other hand, signing for the anchor p2wsh to-remote outputs + // requires the sign descriptor to contain the redeem script ver batim. + // This difference in behavior forces us to use a distinct + // toRemoteSigningScript to handle both cases. + var ( + toRemoteSequence uint32 + toRemoteRedeemScript []byte + toRemoteScriptHash []byte + toRemoteSigningScript []byte + ) + if isAnchorChannel { + toRemoteSequence = 1 + toRemoteRedeemScript, err = input.CommitScriptToRemoteConfirmed( + toRemotePK, + ) + require.Nil(t, err) + + toRemoteScriptHash, err = input.WitnessScriptHash( + toRemoteRedeemScript, + ) + require.Nil(t, err) + + // As it should be. + toRemoteSigningScript = toRemoteRedeemScript + + } else { + toRemoteRedeemScript = toRemotePK.SerializeCompressed() + toRemoteScriptHash, err = input.CommitScriptUnencumbered( + toRemotePK, + ) + require.Nil(t, err) + + // NOTE: This is the _pkscript_. + toRemoteSigningScript = toRemoteScriptHash + } // Construct the breaching commitment txn, containing the to-local and // to-remote outputs. We don't need any inputs for this test. @@ -142,7 +195,11 @@ func testJusticeDescriptor(t *testing.T, blobType blob.Type) { // create signatures using the original weight estimate. weightEstimate.AddWitnessInput(input.ToLocalPenaltyWitnessSize - 1) - weightEstimate.AddWitnessInput(input.P2WKHWitnessSize) + if isAnchorChannel { + weightEstimate.AddWitnessInput(input.ToRemoteConfirmedWitnessSize) + } else { + weightEstimate.AddWitnessInput(input.P2WKHWitnessSize) + } weightEstimate.AddP2WKHOutput() if blobType.Has(blob.FlagReward) { weightEstimate.AddP2WKHOutput() @@ -167,6 +224,7 @@ func testJusticeDescriptor(t *testing.T, blobType blob.Type) { // Begin to assemble the justice kit, starting with the sweep address, // pubkeys, and csv delay. justiceKit := &blob.JusticeKit{ + BlobType: blobType, SweepAddress: makeAddrSlice(22), CSVDelay: csvDelay, } @@ -192,6 +250,7 @@ func testJusticeDescriptor(t *testing.T, blobType blob.Type) { Hash: breachTxID, Index: 1, }, + Sequence: toRemoteSequence, }, }, } @@ -226,7 +285,7 @@ func testJusticeDescriptor(t *testing.T, blobType blob.Type) { KeyLocator: toRemoteKeyLoc, PubKey: toRemotePK, }, - WitnessScript: toRemoteScriptHash, + WitnessScript: toRemoteSigningScript, Output: breachTxn.TxOut[1], SigHashes: hashCache, InputIndex: 1, @@ -245,18 +304,15 @@ func testJusticeDescriptor(t *testing.T, blobType blob.Type) { // 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. - toRemoteWitness, err := input.CommitSpendNoDelay( - signer, toRemoteSignDesc, justiceTxn, false, - ) + toRemoteSigRaw, err := signer.SignOutputRaw(justiceTxn, toRemoteSignDesc) require.Nil(t, err) - toRemoteSigRaw := toRemoteWitness[0][:len(toRemoteWitness[0])-1] // Convert the DER 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. - toRemoteSig, err := lnwire.NewSigFromRawSignature(toRemoteSigRaw) + toRemoteSig, err := lnwire.NewSigFromSignature(toRemoteSigRaw) require.Nil(t, err) // Complete our justice kit by copying the signatures into the payload. @@ -301,9 +357,9 @@ func testJusticeDescriptor(t *testing.T, blobType blob.Type) { // Construct the test's to-remote witness. justiceTxn.TxIn[1].Witness = make([][]byte, 2) - justiceTxn.TxIn[1].Witness[0] = append(toRemoteSigRaw, + justiceTxn.TxIn[1].Witness[0] = append(toRemoteSigRaw.Serialize(), byte(txscript.SigHashAll)) - justiceTxn.TxIn[1].Witness[1] = toRemotePK.SerializeCompressed() + justiceTxn.TxIn[1].Witness[1] = toRemoteRedeemScript // Assert that the watchtower derives the same justice txn. require.Equal(t, justiceTxn, wtJusticeTxn)