lnwallet: support transactions and scripts for new commitment type

This commit modifies the channel state machine to be able to derive the
proper commitment and second-level HTLC output scripts required by the
new script-enforced leased channel commitment type.
This commit is contained in:
Wilmer Paulino 2021-07-14 17:16:13 -07:00 committed by Olaoluwa Osuntokun
parent 01e9bb2bff
commit 8cfb53f64a
No known key found for this signature in database
GPG key ID: 3BBD59E99B280306
11 changed files with 230 additions and 98 deletions

View file

@ -2299,6 +2299,7 @@ func createInitChannels(revocationWindow int) (*lnwallet.LightningChannel, *lnwa
aliceCommitTx, bobCommitTx, err := lnwallet.CreateCommitmentTxns(
channelBal, channelBal, &aliceCfg, &bobCfg, aliceCommitPoint,
bobCommitPoint, *fundingTxIn, channeldb.SingleFunderTweaklessBit,
false, 0,
)
if err != nil {
return nil, nil, nil, err

View file

@ -362,8 +362,13 @@ func (c *chainWatcher) handleUnknownLocalState(
// With the keys derived, we'll construct the remote script that'll be
// present if they have a non-dust balance on the commitment.
var leaseExpiry uint32
if c.cfg.chanState.ChanType.HasLeaseExpiration() {
leaseExpiry = c.cfg.chanState.ThawHeight
}
remoteScript, _, err := lnwallet.CommitScriptToRemote(
c.cfg.chanState.ChanType, commitKeyRing.ToRemoteKey,
c.cfg.chanState.ChanType, c.cfg.chanState.IsInitiator,
commitKeyRing.ToRemoteKey, leaseExpiry,
)
if err != nil {
return false, err
@ -373,8 +378,9 @@ func (c *chainWatcher) handleUnknownLocalState(
// the remote party allowing them to claim this output before the CSV
// delay if we breach.
localScript, err := lnwallet.CommitScriptToSelf(
c.cfg.chanState.ChanType, c.cfg.chanState.IsInitiator,
commitKeyRing.ToLocalKey, commitKeyRing.RevocationKey,
uint32(c.cfg.chanState.LocalChanCfg.CsvDelay),
uint32(c.cfg.chanState.LocalChanCfg.CsvDelay), leaseExpiry,
)
if err != nil {
return false, err

View file

@ -134,6 +134,7 @@ func createTestChannel(alicePrivKey, bobPrivKey []byte,
channelCapacity := aliceAmount + bobAmount
csvTimeoutAlice := uint32(5)
csvTimeoutBob := uint32(4)
isAliceInitiator := true
aliceConstraints := &channeldb.ChannelConstraints{
DustLimit: btcutil.Amount(200),
@ -230,6 +231,7 @@ func createTestChannel(alicePrivKey, bobPrivKey []byte,
aliceCommitTx, bobCommitTx, err := lnwallet.CreateCommitmentTxns(
aliceAmount, bobAmount, &aliceCfg, &bobCfg, aliceCommitPoint,
bobCommitPoint, *fundingTxIn, channeldb.SingleFunderTweaklessBit,
isAliceInitiator, 0,
)
if err != nil {
return nil, nil, nil, err
@ -298,7 +300,7 @@ func createTestChannel(alicePrivKey, bobPrivKey []byte,
IdentityPub: aliceKeyPub,
FundingOutpoint: *prevOut,
ChanType: channeldb.SingleFunderTweaklessBit,
IsInitiator: true,
IsInitiator: isAliceInitiator,
Capacity: channelCapacity,
RemoteCurrentRevocation: bobCommitPoint,
RevocationProducer: alicePreimageProducer,
@ -317,7 +319,7 @@ func createTestChannel(alicePrivKey, bobPrivKey []byte,
IdentityPub: bobKeyPub,
FundingOutpoint: *prevOut,
ChanType: channeldb.SingleFunderTweaklessBit,
IsInitiator: false,
IsInitiator: !isAliceInitiator,
Capacity: channelCapacity,
RemoteCurrentRevocation: aliceCommitPoint,
RevocationProducer: bobPreimageProducer,

View file

@ -861,8 +861,8 @@ func TestWitnessSizes(t *testing.T) {
func genTimeoutTx(chanType channeldb.ChannelType) (*wire.MsgTx, error) {
// Create the unsigned timeout tx.
timeoutTx, err := lnwallet.CreateHtlcTimeoutTx(
chanType, testOutPoint, testAmt, testCLTVExpiry,
testCSVDelay, testPubkey, testPubkey,
chanType, false, testOutPoint, testAmt, testCLTVExpiry,
testCSVDelay, 0, testPubkey, testPubkey,
)
if err != nil {
return nil, err
@ -903,7 +903,7 @@ func genTimeoutTx(chanType channeldb.ChannelType) (*wire.MsgTx, error) {
func genSuccessTx(chanType channeldb.ChannelType) (*wire.MsgTx, error) {
// Create the unisgned success tx.
successTx, err := lnwallet.CreateHtlcSuccessTx(
chanType, testOutPoint, testAmt, testCSVDelay,
chanType, false, testOutPoint, testAmt, testCSVDelay, 0,
testPubkey, testPubkey,
)
if err != nil {

View file

@ -2315,8 +2315,14 @@ func NewBreachRetribution(chanState *channeldb.OpenChannel, stateNum uint64,
// number so we can have the proper witness script to sign and include
// within the final witness.
theirDelay := uint32(chanState.RemoteChanCfg.CsvDelay)
isRemoteInitiator := !chanState.IsInitiator
var leaseExpiry uint32
if chanState.ChanType.HasLeaseExpiration() {
leaseExpiry = chanState.ThawHeight
}
theirScript, err := CommitScriptToSelf(
keyRing.ToLocalKey, keyRing.RevocationKey, theirDelay,
chanState.ChanType, isRemoteInitiator, keyRing.ToLocalKey,
keyRing.RevocationKey, theirDelay, leaseExpiry,
)
if err != nil {
return nil, err
@ -2325,7 +2331,8 @@ func NewBreachRetribution(chanState *channeldb.OpenChannel, stateNum uint64,
// Since it is the remote breach we are reconstructing, the output going
// to us will be a to-remote script with our local params.
ourScript, ourDelay, err := CommitScriptToRemote(
chanState.ChanType, keyRing.ToRemoteKey,
chanState.ChanType, isRemoteInitiator, keyRing.ToRemoteKey,
leaseExpiry,
)
if err != nil {
return nil, err
@ -2410,7 +2417,9 @@ func NewBreachRetribution(chanState *channeldb.OpenChannel, stateNum uint64,
// remote commitment transaction, and *they* go to the second
// level.
secondLevelScript, err := SecondLevelHtlcScript(
chanState.ChanType, isRemoteInitiator,
keyRing.RevocationKey, keyRing.ToLocalKey, theirDelay,
leaseExpiry,
)
if err != nil {
return nil, err
@ -2968,8 +2977,8 @@ func processFeeUpdate(feeUpdate *PaymentDescriptor, nextHeight uint64,
// signature can be submitted to the sigPool to generate all the signatures
// asynchronously and in parallel.
func genRemoteHtlcSigJobs(keyRing *CommitmentKeyRing,
chanType channeldb.ChannelType,
localChanCfg, remoteChanCfg *channeldb.ChannelConfig,
chanType channeldb.ChannelType, isRemoteInitiator bool,
leaseExpiry uint32, localChanCfg, remoteChanCfg *channeldb.ChannelConfig,
remoteCommitView *commitment) ([]SignJob, chan struct{}, error) {
txHash := remoteCommitView.txn.TxHash()
@ -3019,9 +3028,9 @@ func genRemoteHtlcSigJobs(keyRing *CommitmentKeyRing,
Index: uint32(htlc.remoteOutputIndex),
}
sigJob.Tx, err = CreateHtlcTimeoutTx(
chanType, op, outputAmt, htlc.Timeout,
uint32(remoteChanCfg.CsvDelay),
keyRing.RevocationKey, keyRing.ToLocalKey,
chanType, isRemoteInitiator, op, outputAmt,
htlc.Timeout, uint32(remoteChanCfg.CsvDelay),
leaseExpiry, keyRing.RevocationKey, keyRing.ToLocalKey,
)
if err != nil {
return nil, nil, err
@ -3072,7 +3081,8 @@ func genRemoteHtlcSigJobs(keyRing *CommitmentKeyRing,
Index: uint32(htlc.remoteOutputIndex),
}
sigJob.Tx, err = CreateHtlcSuccessTx(
chanType, op, outputAmt, uint32(remoteChanCfg.CsvDelay),
chanType, isRemoteInitiator, op, outputAmt,
uint32(remoteChanCfg.CsvDelay), leaseExpiry,
keyRing.RevocationKey, keyRing.ToLocalKey,
)
if err != nil {
@ -3581,10 +3591,14 @@ func (lc *LightningChannel) SignNextCommitment() (lnwire.Sig, []lnwire.Sig, []ch
// need to generate signatures of each of them for the remote party's
// commitment state. We do so in two phases: first we generate and
// submit the set of signature jobs to the worker pool.
var leaseExpiry uint32
if lc.channelState.ChanType.HasLeaseExpiration() {
leaseExpiry = lc.channelState.ThawHeight
}
sigBatch, cancelChan, err := genRemoteHtlcSigJobs(
keyRing, lc.channelState.ChanType,
&lc.channelState.LocalChanCfg, &lc.channelState.RemoteChanCfg,
newCommitView,
keyRing, lc.channelState.ChanType, !lc.channelState.IsInitiator,
leaseExpiry, &lc.channelState.LocalChanCfg,
&lc.channelState.RemoteChanCfg, newCommitView,
)
if err != nil {
return sig, htlcSigs, nil, err
@ -4067,7 +4081,7 @@ func (lc *LightningChannel) computeView(view *htlcView, remoteChain bool,
// directly into the pool of workers.
func genHtlcSigValidationJobs(localCommitmentView *commitment,
keyRing *CommitmentKeyRing, htlcSigs []lnwire.Sig,
chanType channeldb.ChannelType,
chanType channeldb.ChannelType, isLocalInitiator bool, leaseExpiry uint32,
localChanCfg, remoteChanCfg *channeldb.ChannelConfig) ([]VerifyJob, error) {
txHash := localCommitmentView.txn.TxHash()
@ -4116,9 +4130,10 @@ func genHtlcSigValidationJobs(localCommitmentView *commitment,
outputAmt := htlc.Amount.ToSatoshis() - htlcFee
successTx, err := CreateHtlcSuccessTx(
chanType, op, outputAmt,
uint32(localChanCfg.CsvDelay),
keyRing.RevocationKey, keyRing.ToLocalKey,
chanType, isLocalInitiator, op,
outputAmt, uint32(localChanCfg.CsvDelay),
leaseExpiry, keyRing.RevocationKey,
keyRing.ToLocalKey,
)
if err != nil {
return nil, err
@ -4170,8 +4185,9 @@ func genHtlcSigValidationJobs(localCommitmentView *commitment,
outputAmt := htlc.Amount.ToSatoshis() - htlcFee
timeoutTx, err := CreateHtlcTimeoutTx(
chanType, op, outputAmt, htlc.Timeout,
uint32(localChanCfg.CsvDelay),
chanType, isLocalInitiator, op,
outputAmt, htlc.Timeout,
uint32(localChanCfg.CsvDelay), leaseExpiry,
keyRing.RevocationKey, keyRing.ToLocalKey,
)
if err != nil {
@ -4385,9 +4401,14 @@ func (lc *LightningChannel) ReceiveNewCommitment(commitSig lnwire.Sig,
// As an optimization, we'll generate a series of jobs for the worker
// pool to verify each of the HTLc signatures presented. Once
// generated, we'll submit these jobs to the worker pool.
var leaseExpiry uint32
if lc.channelState.ChanType.HasLeaseExpiration() {
leaseExpiry = lc.channelState.ThawHeight
}
verifyJobs, err := genHtlcSigValidationJobs(
localCommitmentView, keyRing, htlcSigs,
lc.channelState.ChanType, &lc.channelState.LocalChanCfg,
lc.channelState.ChanType, lc.channelState.IsInitiator,
leaseExpiry, &lc.channelState.LocalChanCfg,
&lc.channelState.RemoteChanCfg,
)
if err != nil {
@ -5570,18 +5591,24 @@ func NewUnilateralCloseSummary(chanState *channeldb.OpenChannel, signer input.Si
// First, we'll generate the commitment point and the revocation point
// so we can re-construct the HTLC state and also our payment key.
isOurCommit := false
keyRing := DeriveCommitmentKeys(
commitPoint, false, chanState.ChanType,
commitPoint, isOurCommit, chanState.ChanType,
&chanState.LocalChanCfg, &chanState.RemoteChanCfg,
)
// Next, we'll obtain HTLC resolutions for all the outgoing HTLC's we
// had on their commitment transaction.
var leaseExpiry uint32
if chanState.ChanType.HasLeaseExpiration() {
leaseExpiry = chanState.ThawHeight
}
isRemoteInitiator := !chanState.IsInitiator
htlcResolutions, err := extractHtlcResolutions(
chainfee.SatPerKWeight(remoteCommit.FeePerKw), false, signer,
remoteCommit.Htlcs, keyRing, &chanState.LocalChanCfg,
chainfee.SatPerKWeight(remoteCommit.FeePerKw), isOurCommit,
signer, remoteCommit.Htlcs, keyRing, &chanState.LocalChanCfg,
&chanState.RemoteChanCfg, commitSpend.SpendingTx,
chanState.ChanType,
chanState.ChanType, isRemoteInitiator, leaseExpiry,
)
if err != nil {
return nil, fmt.Errorf("unable to create htlc "+
@ -5594,7 +5621,8 @@ func NewUnilateralCloseSummary(chanState *channeldb.OpenChannel, signer input.Si
// locate the output index of our non-delayed output on the commitment
// transaction.
selfScript, maturityDelay, err := CommitScriptToRemote(
chanState.ChanType, keyRing.ToRemoteKey,
chanState.ChanType, isRemoteInitiator, keyRing.ToRemoteKey,
leaseExpiry,
)
if err != nil {
return nil, fmt.Errorf("unable to create self commit "+
@ -5801,8 +5829,9 @@ type HtlcResolutions struct {
func newOutgoingHtlcResolution(signer input.Signer,
localChanCfg *channeldb.ChannelConfig, commitTx *wire.MsgTx,
htlc *channeldb.HTLC, keyRing *CommitmentKeyRing,
feePerKw chainfee.SatPerKWeight, csvDelay uint32,
localCommit bool, chanType channeldb.ChannelType) (*OutgoingHtlcResolution, error) {
feePerKw chainfee.SatPerKWeight, csvDelay, leaseExpiry uint32,
localCommit, isCommitFromInitiator bool,
chanType channeldb.ChannelType) (*OutgoingHtlcResolution, error) {
op := wire.OutPoint{
Hash: commitTx.TxHash(),
@ -5854,8 +5883,9 @@ func newOutgoingHtlcResolution(signer input.Signer,
// With the fee calculated, re-construct the second level timeout
// transaction.
timeoutTx, err := CreateHtlcTimeoutTx(
chanType, op, secondLevelOutputAmt, htlc.RefundTimeout,
csvDelay, keyRing.RevocationKey, keyRing.ToLocalKey,
chanType, isCommitFromInitiator, op, secondLevelOutputAmt,
htlc.RefundTimeout, csvDelay, leaseExpiry, keyRing.RevocationKey,
keyRing.ToLocalKey,
)
if err != nil {
return nil, err
@ -5901,7 +5931,8 @@ func newOutgoingHtlcResolution(signer input.Signer,
// transaction creates so we can generate the signDesc required to
// complete the claim process after a delay period.
htlcSweepScript, err := SecondLevelHtlcScript(
keyRing.RevocationKey, keyRing.ToLocalKey, csvDelay,
chanType, isCommitFromInitiator, keyRing.RevocationKey,
keyRing.ToLocalKey, csvDelay, leaseExpiry,
)
if err != nil {
return nil, err
@ -5942,8 +5973,9 @@ func newOutgoingHtlcResolution(signer input.Signer,
func newIncomingHtlcResolution(signer input.Signer,
localChanCfg *channeldb.ChannelConfig, commitTx *wire.MsgTx,
htlc *channeldb.HTLC, keyRing *CommitmentKeyRing,
feePerKw chainfee.SatPerKWeight, csvDelay uint32, localCommit bool,
chanType channeldb.ChannelType) (*IncomingHtlcResolution, error) {
feePerKw chainfee.SatPerKWeight, csvDelay, leaseExpiry uint32,
localCommit, isCommitFromInitiator bool, chanType channeldb.ChannelType) (
*IncomingHtlcResolution, error) {
op := wire.OutPoint{
Hash: commitTx.TxHash(),
@ -5988,8 +6020,8 @@ func newIncomingHtlcResolution(signer input.Signer,
htlcFee := HtlcSuccessFee(chanType, feePerKw)
secondLevelOutputAmt := htlc.Amt.ToSatoshis() - htlcFee
successTx, err := CreateHtlcSuccessTx(
chanType, op, secondLevelOutputAmt, csvDelay,
keyRing.RevocationKey, keyRing.ToLocalKey,
chanType, isCommitFromInitiator, op, secondLevelOutputAmt,
csvDelay, leaseExpiry, keyRing.RevocationKey, keyRing.ToLocalKey,
)
if err != nil {
return nil, err
@ -6036,7 +6068,8 @@ func newIncomingHtlcResolution(signer input.Signer,
// creates so we can generate the proper signDesc to sweep it after the
// CSV delay has passed.
htlcSweepScript, err := SecondLevelHtlcScript(
keyRing.RevocationKey, keyRing.ToLocalKey, csvDelay,
chanType, isCommitFromInitiator, keyRing.RevocationKey,
keyRing.ToLocalKey, csvDelay, leaseExpiry,
)
if err != nil {
return nil, err
@ -6096,8 +6129,8 @@ func (r *OutgoingHtlcResolution) HtlcPoint() wire.OutPoint {
func extractHtlcResolutions(feePerKw chainfee.SatPerKWeight, ourCommit bool,
signer input.Signer, htlcs []channeldb.HTLC, keyRing *CommitmentKeyRing,
localChanCfg, remoteChanCfg *channeldb.ChannelConfig,
commitTx *wire.MsgTx, chanType channeldb.ChannelType) (
*HtlcResolutions, error) {
commitTx *wire.MsgTx, chanType channeldb.ChannelType,
isCommitFromInitiator bool, leaseExpiry uint32) (*HtlcResolutions, error) {
// TODO(roasbeef): don't need to swap csv delay?
dustLimit := remoteChanCfg.DustLimit
@ -6129,8 +6162,8 @@ func extractHtlcResolutions(feePerKw chainfee.SatPerKWeight, ourCommit bool,
// as we can satisfy the contract.
ihr, err := newIncomingHtlcResolution(
signer, localChanCfg, commitTx, &htlc,
keyRing, feePerKw, uint32(csvDelay), ourCommit,
chanType,
keyRing, feePerKw, uint32(csvDelay), leaseExpiry,
ourCommit, isCommitFromInitiator, chanType,
)
if err != nil {
return nil, err
@ -6142,7 +6175,8 @@ func extractHtlcResolutions(feePerKw chainfee.SatPerKWeight, ourCommit bool,
ohr, err := newOutgoingHtlcResolution(
signer, localChanCfg, commitTx, &htlc, keyRing,
feePerKw, uint32(csvDelay), ourCommit, chanType,
feePerKw, uint32(csvDelay), leaseExpiry, ourCommit,
isCommitFromInitiator, chanType,
)
if err != nil {
return nil, err
@ -6281,8 +6315,13 @@ func NewLocalForceCloseSummary(chanState *channeldb.OpenChannel,
&chanState.LocalChanCfg, &chanState.RemoteChanCfg,
)
var leaseExpiry uint32
if chanState.ChanType.HasLeaseExpiration() {
leaseExpiry = chanState.ThawHeight
}
toLocalScript, err := CommitScriptToSelf(
keyRing.ToLocalKey, keyRing.RevocationKey, csvTimeout,
chanState.ChanType, chanState.IsInitiator, keyRing.ToLocalKey,
keyRing.RevocationKey, csvTimeout, leaseExpiry,
)
if err != nil {
return nil, err
@ -6343,6 +6382,7 @@ func NewLocalForceCloseSummary(chanState *channeldb.OpenChannel,
chainfee.SatPerKWeight(localCommit.FeePerKw), true, signer,
localCommit.Htlcs, keyRing, &chanState.LocalChanCfg,
&chanState.RemoteChanCfg, commitTx, chanState.ChanType,
chanState.IsInitiator, leaseExpiry,
)
if err != nil {
return nil, err

View file

@ -192,14 +192,31 @@ type ScriptInfo struct {
// CommitScriptToSelf constructs the public key script for the output on the
// commitment transaction paying to the "owner" of said commitment transaction.
// If the other party learns of the preimage to the revocation hash, then they
// can claim all the settled funds in the channel, plus the unsettled funds.
func CommitScriptToSelf(selfKey, revokeKey *btcec.PublicKey, csvDelay uint32) (
// The `initiator` argument should correspond to the owner of the commitment
// tranasction which we are generating the to_local script for. If the other
// party learns of the preimage to the revocation hash, then they can claim all
// the settled funds in the channel, plus the unsettled funds.
func CommitScriptToSelf(chanType channeldb.ChannelType, initiator bool,
selfKey, revokeKey *btcec.PublicKey, csvDelay, leaseExpiry uint32) (
*ScriptInfo, error) {
toLocalRedeemScript, err := input.CommitScriptToSelf(
csvDelay, selfKey, revokeKey,
var (
toLocalRedeemScript []byte
err error
)
switch {
// If we are the initiator of a leased channel, then we have an
// additional CLTV requirement in addition to the usual CSV requirement.
case initiator && chanType.HasLeaseExpiration():
toLocalRedeemScript, err = input.LeaseCommitScriptToSelf(
selfKey, revokeKey, csvDelay, leaseExpiry,
)
default:
toLocalRedeemScript, err = input.CommitScriptToSelf(
csvDelay, selfKey, revokeKey,
)
}
if err != nil {
return nil, err
}
@ -216,14 +233,38 @@ func CommitScriptToSelf(selfKey, revokeKey *btcec.PublicKey, csvDelay uint32) (
}
// CommitScriptToRemote derives the appropriate to_remote script based on the
// channel's commitment type. The second return value is the CSV delay of the
// output script, what must be satisfied in order to spend the output.
func CommitScriptToRemote(chanType channeldb.ChannelType,
key *btcec.PublicKey) (*ScriptInfo, uint32, error) {
// channel's commitment type. The `initiator` argument should correspond to the
// owner of the commitment tranasction which we are generating the to_remote
// script for. The second return value is the CSV delay of the output script,
// what must be satisfied in order to spend the output.
func CommitScriptToRemote(chanType channeldb.ChannelType, initiator bool,
key *btcec.PublicKey, leaseExpiry uint32) (*ScriptInfo, uint32, error) {
switch {
// If we are not the initiator of a leased channel, then the remote
// party has an additional CLTV requirement in addition to the 1 block
// CSV requirement.
case chanType.HasLeaseExpiration() && !initiator:
script, err := input.LeaseCommitScriptToRemoteConfirmed(
key, leaseExpiry,
)
if err != nil {
return nil, 0, err
}
p2wsh, err := input.WitnessScriptHash(script)
if err != nil {
return nil, 0, err
}
return &ScriptInfo{
PkScript: p2wsh,
WitnessScript: script,
}, 1, nil
// If this channel type has anchors, we derive the delayed to_remote
// script.
if chanType.HasAnchors() {
case chanType.HasAnchors():
script, err := input.CommitScriptToRemoteConfirmed(key)
if err != nil {
return nil, 0, err
@ -238,20 +279,22 @@ func CommitScriptToRemote(chanType channeldb.ChannelType,
PkScript: p2wsh,
WitnessScript: script,
}, 1, nil
}
// Otherwise the to_remote will be a simple p2wkh.
p2wkh, err := input.CommitScriptUnencumbered(key)
if err != nil {
return nil, 0, err
}
default:
// Otherwise the to_remote will be a simple p2wkh.
p2wkh, err := input.CommitScriptUnencumbered(key)
if err != nil {
return nil, 0, err
}
// Since this is a regular P2WKH, the WitnessScipt and PkScript should
// both be set to the script hash.
return &ScriptInfo{
WitnessScript: p2wkh,
PkScript: p2wkh,
}, 0, nil
// Since this is a regular P2WKH, the WitnessScipt and PkScript
// should both be set to the script hash.
return &ScriptInfo{
WitnessScript: p2wkh,
PkScript: p2wkh,
}, 0, nil
}
}
// HtlcSigHashType returns the sighash type to use for HTLC success and timeout
@ -296,13 +339,30 @@ func HtlcSecondLevelInputSequence(chanType channeldb.ChannelType) uint32 {
// on the channel's commitment type. It is the uniform script that's used as the
// output for the second-level HTLC transactions. The second level transaction
// act as a sort of covenant, ensuring that a 2-of-2 multi-sig output can only
// be spent in a particular way, and to a particular output.
func SecondLevelHtlcScript(revocationKey, delayKey *btcec.PublicKey,
csvDelay uint32) (*ScriptInfo, error) {
// be spent in a particular way, and to a particular output. The `initiator`
// argument should correspond to the owner of the commitment tranasction which
// we are generating the to_local script for.
func SecondLevelHtlcScript(chanType channeldb.ChannelType, initiator bool,
revocationKey, delayKey *btcec.PublicKey,
csvDelay, leaseExpiry uint32) (*ScriptInfo, error) {
witnessScript, err := input.SecondLevelHtlcScript(
revocationKey, delayKey, csvDelay,
var (
witnessScript []byte
err error
)
switch {
// If we are the initiator of a leased channel, then we have an
// additional CLTV requirement in addition to the usual CSV requirement.
case initiator && chanType.HasLeaseExpiration():
witnessScript, err = input.LeaseSecondLevelHtlcScript(
revocationKey, delayKey, csvDelay, leaseExpiry,
)
default:
witnessScript, err = input.SecondLevelHtlcScript(
revocationKey, delayKey, csvDelay,
)
}
if err != nil {
return nil, err
}
@ -549,19 +609,23 @@ func (cb *CommitmentBuilder) createUnsignedCommitmentTx(ourBalance,
// CreateCommitTx with parameters matching the perspective, to generate
// a new commitment transaction with all the latest unsettled/un-timed
// out HTLCs.
var leaseExpiry uint32
if cb.chanState.ChanType.HasLeaseExpiration() {
leaseExpiry = cb.chanState.ThawHeight
}
if isOurs {
commitTx, err = CreateCommitTx(
cb.chanState.ChanType, fundingTxIn(cb.chanState), keyRing,
&cb.chanState.LocalChanCfg, &cb.chanState.RemoteChanCfg,
ourBalance.ToSatoshis(), theirBalance.ToSatoshis(),
numHTLCs,
numHTLCs, cb.chanState.IsInitiator, leaseExpiry,
)
} else {
commitTx, err = CreateCommitTx(
cb.chanState.ChanType, fundingTxIn(cb.chanState), keyRing,
&cb.chanState.RemoteChanCfg, &cb.chanState.LocalChanCfg,
theirBalance.ToSatoshis(), ourBalance.ToSatoshis(),
numHTLCs,
numHTLCs, !cb.chanState.IsInitiator, leaseExpiry,
)
}
if err != nil {
@ -660,12 +724,13 @@ func (cb *CommitmentBuilder) createUnsignedCommitmentTx(ourBalance,
// output paying to the "owner" of the commitment transaction which can be
// spent after a relative block delay or revocation event, and a remote output
// paying the counterparty within the channel, which can be spent immediately
// or after a delay depending on the commitment type..
// or after a delay depending on the commitment type. The `initiator` argument
// should correspond to the owner of the commitment tranasction we are creating.
func CreateCommitTx(chanType channeldb.ChannelType,
fundingOutput wire.TxIn, keyRing *CommitmentKeyRing,
localChanCfg, remoteChanCfg *channeldb.ChannelConfig,
amountToLocal, amountToRemote btcutil.Amount,
numHTLCs int64) (*wire.MsgTx, error) {
numHTLCs int64, initiator bool, leaseExpiry uint32) (*wire.MsgTx, error) {
// First, we create the script for the delayed "pay-to-self" output.
// This output has 2 main redemption clauses: either we can redeem the
@ -673,8 +738,8 @@ func CreateCommitTx(chanType channeldb.ChannelType,
// the funds with the revocation key if we broadcast a revoked
// commitment transaction.
toLocalScript, err := CommitScriptToSelf(
keyRing.ToLocalKey, keyRing.RevocationKey,
uint32(localChanCfg.CsvDelay),
chanType, initiator, keyRing.ToLocalKey, keyRing.RevocationKey,
uint32(localChanCfg.CsvDelay), leaseExpiry,
)
if err != nil {
return nil, err
@ -682,7 +747,7 @@ func CreateCommitTx(chanType channeldb.ChannelType,
// Next, we create the script paying to the remote.
toRemoteScript, _, err := CommitScriptToRemote(
chanType, keyRing.ToRemoteKey,
chanType, initiator, keyRing.ToRemoteKey, leaseExpiry,
)
if err != nil {
return nil, err

View file

@ -119,6 +119,7 @@ func CreateTestChannels(chanType channeldb.ChannelType) (
channelBal := channelCapacity / 2
csvTimeoutAlice := uint32(5)
csvTimeoutBob := uint32(4)
isAliceInitiator := true
prevOut := &wire.OutPoint{
Hash: chainhash.Hash(testHdSeed),
@ -223,7 +224,7 @@ func CreateTestChannels(chanType channeldb.ChannelType) (
aliceCommitTx, bobCommitTx, err := CreateCommitmentTxns(
channelBal, channelBal, &aliceCfg, &bobCfg, aliceCommitPoint,
bobCommitPoint, *fundingTxIn, chanType,
bobCommitPoint, *fundingTxIn, chanType, isAliceInitiator, 0,
)
if err != nil {
return nil, nil, nil, err
@ -318,7 +319,7 @@ func CreateTestChannels(chanType channeldb.ChannelType) (
FundingOutpoint: *prevOut,
ShortChannelID: shortChanID,
ChanType: chanType,
IsInitiator: true,
IsInitiator: isAliceInitiator,
Capacity: channelCapacity,
RemoteCurrentRevocation: bobCommitPoint,
RevocationProducer: alicePreimageProducer,
@ -336,7 +337,7 @@ func CreateTestChannels(chanType channeldb.ChannelType) (
FundingOutpoint: *prevOut,
ShortChannelID: shortChanID,
ChanType: chanType,
IsInitiator: false,
IsInitiator: !isAliceInitiator,
Capacity: channelCapacity,
RemoteCurrentRevocation: aliceCommitPoint,
RevocationProducer: bobPreimageProducer,

View file

@ -44,9 +44,10 @@ var (
// In order to spend the HTLC output, the witness for the passed transaction
// should be:
// * <0> <sender sig> <recvr sig> <preimage>
func CreateHtlcSuccessTx(chanType channeldb.ChannelType,
htlcOutput wire.OutPoint, htlcAmt btcutil.Amount, csvDelay uint32,
revocationKey, delayKey *btcec.PublicKey) (*wire.MsgTx, error) {
func CreateHtlcSuccessTx(chanType channeldb.ChannelType, initiator bool,
htlcOutput wire.OutPoint, htlcAmt btcutil.Amount, csvDelay,
leaseExpiry uint32, revocationKey, delayKey *btcec.PublicKey) (
*wire.MsgTx, error) {
// Create a version two transaction (as the success version of this
// spends an output with a CSV timeout).
@ -65,7 +66,8 @@ func CreateHtlcSuccessTx(chanType channeldb.ChannelType,
// level HTLC which forces a covenant w.r.t what can be done with all
// HTLC outputs.
script, err := SecondLevelHtlcScript(
revocationKey, delayKey, csvDelay,
chanType, initiator, revocationKey, delayKey, csvDelay,
leaseExpiry,
)
if err != nil {
return nil, err
@ -97,9 +99,9 @@ func CreateHtlcSuccessTx(chanType channeldb.ChannelType,
// NOTE: The passed amount for the HTLC should take into account the required
// fee rate at the time the HTLC was created. The fee should be able to
// entirely pay for this (tiny: 1-in 1-out) transaction.
func CreateHtlcTimeoutTx(chanType channeldb.ChannelType,
func CreateHtlcTimeoutTx(chanType channeldb.ChannelType, initiator bool,
htlcOutput wire.OutPoint, htlcAmt btcutil.Amount,
cltvExpiry, csvDelay uint32,
cltvExpiry, csvDelay, leaseExpiry uint32,
revocationKey, delayKey *btcec.PublicKey) (*wire.MsgTx, error) {
// Create a version two transaction (as the success version of this
@ -123,7 +125,8 @@ func CreateHtlcTimeoutTx(chanType channeldb.ChannelType,
// level HTLC which forces a covenant w.r.t what can be done with all
// HTLC outputs.
script, err := SecondLevelHtlcScript(
revocationKey, delayKey, csvDelay,
chanType, initiator, revocationKey, delayKey, csvDelay,
leaseExpiry,
)
if err != nil {
return nil, err

View file

@ -573,7 +573,7 @@ func testSpendValidation(t *testing.T, tweakless bool) {
}
commitmentTx, err := CreateCommitTx(
channelType, *fakeFundingTxIn, keyRing, aliceChanCfg,
bobChanCfg, channelBalance, channelBalance, 0,
bobChanCfg, channelBalance, channelBalance, 0, true, 0,
)
if err != nil {
t.Fatalf("unable to create commitment transaction: %v", nil)
@ -889,7 +889,7 @@ func createTestChannelsForVectors(tc *testContext, chanType channeldb.ChannelTyp
remoteCommitTx, localCommitTx, err := CreateCommitmentTxns(
remoteBalance, localBalance-commitFee,
&remoteCfg, &localCfg, remoteCommitPoint,
localCommitPoint, *fundingTxIn, chanType,
localCommitPoint, *fundingTxIn, chanType, true, 0,
)
require.NoError(t, err)

View file

@ -1246,8 +1246,8 @@ func (l *LightningWallet) handleFundingCancelRequest(req *fundingReserveCancelMs
func CreateCommitmentTxns(localBalance, remoteBalance btcutil.Amount,
ourChanCfg, theirChanCfg *channeldb.ChannelConfig,
localCommitPoint, remoteCommitPoint *btcec.PublicKey,
fundingTxIn wire.TxIn, chanType channeldb.ChannelType) (
*wire.MsgTx, *wire.MsgTx, error) {
fundingTxIn wire.TxIn, chanType channeldb.ChannelType, initiator bool,
leaseExpiry uint32) (*wire.MsgTx, *wire.MsgTx, error) {
localCommitmentKeys := DeriveCommitmentKeys(
localCommitPoint, true, chanType, ourChanCfg, theirChanCfg,
@ -1258,7 +1258,8 @@ func CreateCommitmentTxns(localBalance, remoteBalance btcutil.Amount,
ourCommitTx, err := CreateCommitTx(
chanType, fundingTxIn, localCommitmentKeys, ourChanCfg,
theirChanCfg, localBalance, remoteBalance, 0,
theirChanCfg, localBalance, remoteBalance, 0, initiator,
leaseExpiry,
)
if err != nil {
return nil, nil, err
@ -1271,7 +1272,8 @@ func CreateCommitmentTxns(localBalance, remoteBalance btcutil.Amount,
theirCommitTx, err := CreateCommitTx(
chanType, fundingTxIn, remoteCommitmentKeys, theirChanCfg,
ourChanCfg, remoteBalance, localBalance, 0,
ourChanCfg, remoteBalance, localBalance, 0, !initiator,
leaseExpiry,
)
if err != nil {
return nil, nil, err
@ -1522,12 +1524,17 @@ func (l *LightningWallet) handleChanPointReady(req *continueContributionMsg) {
// With the funding tx complete, create both commitment transactions.
localBalance := pendingReservation.partialState.LocalCommitment.LocalBalance.ToSatoshis()
remoteBalance := pendingReservation.partialState.LocalCommitment.RemoteBalance.ToSatoshis()
var leaseExpiry uint32
if pendingReservation.partialState.ChanType.HasLeaseExpiration() {
leaseExpiry = pendingReservation.partialState.ThawHeight
}
ourCommitTx, theirCommitTx, err := CreateCommitmentTxns(
localBalance, remoteBalance, ourContribution.ChannelConfig,
theirContribution.ChannelConfig,
ourContribution.FirstCommitmentPoint,
theirContribution.FirstCommitmentPoint, fundingTxIn,
pendingReservation.partialState.ChanType,
pendingReservation.partialState.IsInitiator, leaseExpiry,
)
if err != nil {
req.err <- err
@ -1894,6 +1901,10 @@ func (l *LightningWallet) handleSingleFunderSigs(req *addSingleFunderSigsMsg) {
// remote node's commitment transactions.
localBalance := pendingReservation.partialState.LocalCommitment.LocalBalance.ToSatoshis()
remoteBalance := pendingReservation.partialState.LocalCommitment.RemoteBalance.ToSatoshis()
var leaseExpiry uint32
if pendingReservation.partialState.ChanType.HasLeaseExpiration() {
leaseExpiry = pendingReservation.partialState.ThawHeight
}
ourCommitTx, theirCommitTx, err := CreateCommitmentTxns(
localBalance, remoteBalance,
pendingReservation.ourContribution.ChannelConfig,
@ -1901,6 +1912,7 @@ func (l *LightningWallet) handleSingleFunderSigs(req *addSingleFunderSigsMsg) {
pendingReservation.ourContribution.FirstCommitmentPoint,
pendingReservation.theirContribution.FirstCommitmentPoint,
*fundingTxIn, pendingReservation.partialState.ChanType,
pendingReservation.partialState.IsInitiator, leaseExpiry,
)
if err != nil {
req.err <- err

View file

@ -79,6 +79,7 @@ func createTestPeer(notifier chainntnfs.ChainNotifier,
bobDustLimit := btcutil.Amount(1300)
csvTimeoutAlice := uint32(5)
csvTimeoutBob := uint32(4)
isAliceInitiator := true
prevOut := &wire.OutPoint{
Hash: channels.TestHdSeed,
@ -162,6 +163,7 @@ func createTestPeer(notifier chainntnfs.ChainNotifier,
aliceCommitTx, bobCommitTx, err := lnwallet.CreateCommitmentTxns(
channelBal, channelBal, &aliceCfg, &bobCfg, aliceCommitPoint,
bobCommitPoint, *fundingTxIn, channeldb.SingleFunderTweaklessBit,
isAliceInitiator, 0,
)
if err != nil {
return nil, nil, nil, err
@ -229,7 +231,7 @@ func createTestPeer(notifier chainntnfs.ChainNotifier,
FundingOutpoint: *prevOut,
ShortChannelID: shortChanID,
ChanType: channeldb.SingleFunderTweaklessBit,
IsInitiator: true,
IsInitiator: isAliceInitiator,
Capacity: channelCapacity,
RemoteCurrentRevocation: bobCommitPoint,
RevocationProducer: alicePreimageProducer,
@ -246,7 +248,7 @@ func createTestPeer(notifier chainntnfs.ChainNotifier,
IdentityPub: bobKeyPub,
FundingOutpoint: *prevOut,
ChanType: channeldb.SingleFunderTweaklessBit,
IsInitiator: false,
IsInitiator: !isAliceInitiator,
Capacity: channelCapacity,
RemoteCurrentRevocation: aliceCommitPoint,
RevocationProducer: bobPreimageProducer,