From 8c0deb81c27d5492e21f33735d4c2ad4bd5c48ce Mon Sep 17 00:00:00 2001 From: "Johan T. Halseth" Date: Fri, 6 Mar 2020 16:11:45 +0100 Subject: [PATCH] input+lnwallet: Add scripts for CSV delayed HTLC outputs We also increase the witness size for these types to account for the 3 extra bytes. The size won't be correct in all cases, but it is just an upper bound in any case. --- input/script_utils.go | 29 +++++++++++++++++++++++++++-- input/script_utils_test.go | 4 ++-- input/size.go | 22 ++++++++++++++-------- lnwallet/commitment.go | 8 ++++---- sweep/txgenerator_test.go | 2 +- 5 files changed, 48 insertions(+), 17 deletions(-) diff --git a/input/script_utils.go b/input/script_utils.go index 8b47b056..7342e2e6 100644 --- a/input/script_utils.go +++ b/input/script_utils.go @@ -150,6 +150,9 @@ func Ripemd160H(d []byte) []byte { // * The receiver of the HTLC sweeping all the funds in the case that a // revoked commitment transaction bearing this HTLC was broadcast. // +// If confirmedSpend=true, a 1 OP_CSV check will be added to the non-revocation +// cases, to allow sweeping only after confirmation. +// // Possible Input Scripts: // SENDR: <0> <0> (spend using HTLC timeout transaction) // RECVR: @@ -168,9 +171,11 @@ func Ripemd160H(d []byte) []byte { // OP_HASH160 OP_EQUALVERIFY // OP_CHECKSIG // OP_ENDIF +// [1 OP_CHECKSEQUENCEVERIFY OP_DROP] <- if allowing confirmed spend only. // OP_ENDIF func SenderHTLCScript(senderHtlcKey, receiverHtlcKey, - revocationKey *btcec.PublicKey, paymentHash []byte) ([]byte, error) { + revocationKey *btcec.PublicKey, paymentHash []byte, + confirmedSpend bool) ([]byte, error) { builder := txscript.NewScriptBuilder() @@ -243,6 +248,14 @@ func SenderHTLCScript(senderHtlcKey, receiverHtlcKey, // Close out the OP_IF statement above. builder.AddOp(txscript.OP_ENDIF) + // Add 1 block CSV delay if a confirmation is required for the + // non-revocation clauses. + if confirmedSpend { + builder.AddOp(txscript.OP_1) + builder.AddOp(txscript.OP_CHECKSEQUENCEVERIFY) + builder.AddOp(txscript.OP_DROP) + } + // Close out the OP_IF statement at the top of the script. builder.AddOp(txscript.OP_ENDIF) @@ -362,6 +375,9 @@ func SenderHtlcSpendTimeout(receiverSig []byte, signer Signer, // * The sender of the HTLC sweeps the HTLC on-chain after the timeout period // of the HTLC has passed. // +// If confirmedSpend=true, a 1 OP_CSV check will be added to the non-revocation +// cases, to allow sweeping only after confirmation. +// // Possible Input Scripts: // RECVR: <0> (spend using HTLC success transaction) // REVOK: @@ -381,10 +397,11 @@ func SenderHtlcSpendTimeout(receiverSig []byte, signer Signer, // OP_DROP OP_CHECKLOCKTIMEVERIFY OP_DROP // OP_CHECKSIG // OP_ENDIF +// [1 OP_CHECKSEQUENCEVERIFY OP_DROP] <- if allowing confirmed spend only. // OP_ENDIF func ReceiverHTLCScript(cltvExpiry uint32, senderHtlcKey, receiverHtlcKey, revocationKey *btcec.PublicKey, - paymentHash []byte) ([]byte, error) { + paymentHash []byte, confirmedSpend bool) ([]byte, error) { builder := txscript.NewScriptBuilder() @@ -467,6 +484,14 @@ func ReceiverHTLCScript(cltvExpiry uint32, senderHtlcKey, // Close out the inner if statement. builder.AddOp(txscript.OP_ENDIF) + // Add 1 block CSV delay for non-revocation clauses if confirmation is + // required. + if confirmedSpend { + builder.AddOp(txscript.OP_1) + builder.AddOp(txscript.OP_CHECKSEQUENCEVERIFY) + builder.AddOp(txscript.OP_DROP) + } + // Close out the outer if statement. builder.AddOp(txscript.OP_ENDIF) diff --git a/input/script_utils_test.go b/input/script_utils_test.go index dcfc9d7d..955cfecd 100644 --- a/input/script_utils_test.go +++ b/input/script_utils_test.go @@ -235,7 +235,7 @@ func TestHTLCSenderSpendValidation(t *testing.T) { // counterpart. htlcWitnessScript, err = SenderHTLCScript( aliceLocalKey, bobLocalKey, revocationKey, - paymentHash[:], + paymentHash[:], false, ) if err != nil { t.Fatalf("unable to create htlc sender script: %v", err) @@ -496,7 +496,7 @@ func TestHTLCReceiverSpendValidation(t *testing.T) { // counterpart. htlcWitnessScript, err = ReceiverHTLCScript( cltvTimeout, aliceLocalKey, bobLocalKey, revocationKey, - paymentHash[:], + paymentHash[:], false, ) if err != nil { t.Fatalf("unable to create htlc sender script: %v", err) diff --git a/input/size.go b/input/size.go index b0d28aa2..2bea38ce 100644 --- a/input/size.go +++ b/input/size.go @@ -213,7 +213,7 @@ const ( // - witness_script (to_local_script) ToLocalPenaltyWitnessSize = 1 + 1 + 73 + 1 + 1 + ToLocalScriptSize - // AcceptedHtlcScriptSize 139 bytes + // AcceptedHtlcScriptSize 142 bytes // - OP_DUP: 1 byte // - OP_HASH160: 1 byte // - OP_DATA: 1 byte (RIPEMD160(SHA256(revocationkey)) length) @@ -247,11 +247,14 @@ const ( // - OP_DROP: 1 byte // - OP_CHECKSIG: 1 byte // - OP_ENDIF: 1 byte + // - OP_1: 1 byte // These 3 extra bytes are used for both confirmed and regular + // - OP_CSV: 1 byte // HTLC script types. The size won't be correct in all cases, + // - OP_DROP: 1 byte // but it is just an upper bound used for fee estimation in any case. // - OP_ENDIF: 1 byte AcceptedHtlcScriptSize = 3*1 + 20 + 5*1 + 33 + 7*1 + 20 + 4*1 + - 33 + 5*1 + 4 + 5*1 + 33 + 5*1 + 4 + 8*1 - // AcceptedHtlcTimeoutWitnessSize 216 + // AcceptedHtlcTimeoutWitnessSize 219 // - number_of_witness_elements: 1 byte // - sender_sig_length: 1 byte // - sender_sig: 73 bytes @@ -260,7 +263,7 @@ const ( // - witness_script: (accepted_htlc_script) AcceptedHtlcTimeoutWitnessSize = 1 + 1 + 73 + 1 + 1 + AcceptedHtlcScriptSize - // AcceptedHtlcPenaltyWitnessSize 249 bytes + // AcceptedHtlcPenaltyWitnessSize 252 bytes // - number_of_witness_elements: 1 byte // - revocation_sig_length: 1 byte // - revocation_sig: 73 bytes @@ -270,7 +273,7 @@ const ( // - witness_script (accepted_htlc_script) AcceptedHtlcPenaltyWitnessSize = 1 + 1 + 73 + 1 + 33 + 1 + AcceptedHtlcScriptSize - // OfferedHtlcScriptSize 133 bytes + // OfferedHtlcScriptSize 136 bytes // - OP_DUP: 1 byte // - OP_HASH160: 1 byte // - OP_DATA: 1 byte (RIPEMD160(SHA256(revocationkey)) length) @@ -301,10 +304,13 @@ const ( // - OP_EQUALVERIFY: 1 byte // - OP_CHECKSIG: 1 byte // - OP_ENDIF: 1 byte + // - OP_1: 1 byte + // - OP_CSV: 1 byte + // - OP_DROP: 1 byte // - OP_ENDIF: 1 byte - OfferedHtlcScriptSize = 3*1 + 20 + 5*1 + 33 + 10*1 + 33 + 5*1 + 20 + 4*1 + OfferedHtlcScriptSize = 3*1 + 20 + 5*1 + 33 + 10*1 + 33 + 5*1 + 20 + 7*1 - // OfferedHtlcSuccessWitnessSize 317 bytes + // OfferedHtlcSuccessWitnessSize 320 bytes // - number_of_witness_elements: 1 byte // - nil_length: 1 byte // - receiver_sig_length: 1 byte @@ -317,7 +323,7 @@ const ( // - witness_script (offered_htlc_script) OfferedHtlcSuccessWitnessSize = 1 + 1 + 1 + 73 + 1 + 73 + 1 + 32 + 1 + OfferedHtlcScriptSize - // OfferedHtlcPenaltyWitnessSize 243 bytes + // OfferedHtlcPenaltyWitnessSize 246 bytes // - number_of_witness_elements: 1 byte // - revocation_sig_length: 1 byte // - revocation_sig: 73 bytes diff --git a/lnwallet/commitment.go b/lnwallet/commitment.go index 5d56fcd9..2f8b93ac 100644 --- a/lnwallet/commitment.go +++ b/lnwallet/commitment.go @@ -510,28 +510,28 @@ func genHtlcScript(chanType channeldb.ChannelType, isIncoming, ourCommit bool, case isIncoming && ourCommit: witnessScript, err = input.ReceiverHTLCScript(timeout, keyRing.RemoteHtlcKey, keyRing.LocalHtlcKey, - keyRing.RevocationKey, rHash[:]) + keyRing.RevocationKey, rHash[:], false) // We're being paid via an HTLC by the remote party, and the HTLC is // being added to their commitment transaction, so we use the sender's // version of the HTLC script. case isIncoming && !ourCommit: witnessScript, err = input.SenderHTLCScript(keyRing.RemoteHtlcKey, - keyRing.LocalHtlcKey, keyRing.RevocationKey, rHash[:]) + keyRing.LocalHtlcKey, keyRing.RevocationKey, rHash[:], false) // We're sending an HTLC which is being added to our commitment // transaction. Therefore, we need to use the sender's version of the // HTLC script. case !isIncoming && ourCommit: witnessScript, err = input.SenderHTLCScript(keyRing.LocalHtlcKey, - keyRing.RemoteHtlcKey, keyRing.RevocationKey, rHash[:]) + keyRing.RemoteHtlcKey, keyRing.RevocationKey, rHash[:], false) // Finally, we're paying the remote party via an HTLC, which is being // added to their commitment transaction. Therefore, we use the // receiver's version of the HTLC script. case !isIncoming && !ourCommit: witnessScript, err = input.ReceiverHTLCScript(timeout, keyRing.LocalHtlcKey, - keyRing.RemoteHtlcKey, keyRing.RevocationKey, rHash[:]) + keyRing.RemoteHtlcKey, keyRing.RevocationKey, rHash[:], false) } if err != nil { return nil, nil, err diff --git a/sweep/txgenerator_test.go b/sweep/txgenerator_test.go index 84484283..726347ec 100644 --- a/sweep/txgenerator_test.go +++ b/sweep/txgenerator_test.go @@ -14,7 +14,7 @@ var ( input.HtlcOfferedRemoteTimeout, input.WitnessKeyHash, } - expectedWeight = int64(1459) + expectedWeight = int64(1462) expectedSummary = "1 CommitmentTimeLock, 1 " + "HtlcAcceptedSuccessSecondLevel, 1 HtlcOfferedRemoteTimeout, " + "1 WitnessKeyHash"