lnwallet: Refactor commitment key generation code.

Create struct holding all commitment keys to clean up code and avoid
deriving keys multiple times.
This commit is contained in:
Jim Posen 2017-09-22 14:15:01 -07:00 committed by Olaoluwa Osuntokun
parent 0994852396
commit 3151a3a596

@ -292,6 +292,41 @@ type commitment struct {
incomingHTLCIndex map[int32]*PaymentDescriptor incomingHTLCIndex map[int32]*PaymentDescriptor
} }
// commitmentKeyRing holds all derived keys needed to construct commitment and
// HTLC transactions. The keys are derived differently depending whether the
// commitment transaction is ours or the remote peer's. Private keys associated
// with each key may belong to the commitment owner or the "other party" which
// is referred to in the field comments, regardless of which is local and which
// is remote.
type commitmentKeyRing struct {
// commitPoint is the "per commitment point" used to derive the tweak for
// each base point.
commitPoint *btcec.PublicKey
// localKeyTweak is the tweak used to derive the local public key from the
// local payment base point or the local private key from the base point
// secret. This may be included in a SignDescriptor to generate signatures
// for the local payment key.
localKeyTweak []byte
// delayKey is the commitment transaction owner's key which is included in
// HTLC success and timeout transaction scripts.
delayKey *btcec.PublicKey
// paymentKey is the other party's payment key in the commitment tx.
paymentKey *btcec.PublicKey
// revocationKey is the key that can be used by the other party to redeem
// outputs from a revoked commitment transaction if it were to be published.
revocationKey *btcec.PublicKey
// localKey is this node's payment key in the commitment tx.
localKey *btcec.PublicKey
// remoteKey is the remote node's payment key in the commitment tx.
remoteKey *btcec.PublicKey
}
// locateOutputIndex is a small helper function to locate the output index of a // locateOutputIndex is a small helper function to locate the output index of a
// particular HTLC within the current commitment transaction. The duplicate map // particular HTLC within the current commitment transaction. The duplicate map
// massed in is to be retained for each output within the commitment // massed in is to be retained for each output within the commitment
@ -1115,27 +1150,15 @@ func newBreachRetribution(chanState *channeldb.OpenChannel, stateNum uint64,
// With the commitment point generated, we can now generate the four // With the commitment point generated, we can now generate the four
// keys we'll need to reconstruct the commitment state, // keys we'll need to reconstruct the commitment state,
localKey := TweakPubKey(chanState.LocalChanCfg.PaymentBasePoint, keyRing := deriveCommitmentKeys(commitmentPoint, false,
commitmentPoint) &chanState.LocalChanCfg, &chanState.RemoteChanCfg)
remoteKey := TweakPubKey(chanState.RemoteChanCfg.PaymentBasePoint,
commitmentPoint)
remoteDelayKey := TweakPubKey(chanState.RemoteChanCfg.DelayBasePoint,
commitmentPoint)
// Once we derive the revocation leaf, we can then re-create the
// revocation public key used within this state. This is needed in
// order to create the proper script below.
revocationKey := DeriveRevocationPubkey(
chanState.LocalChanCfg.RevocationBasePoint,
commitmentPoint,
)
// Next, reconstruct the scripts as they were present at this state // Next, reconstruct the scripts as they were present at this state
// number so we can have the proper witness script to sign and include // number so we can have the proper witness script to sign and include
// within the final witness. // within the final witness.
remoteDelay := uint32(chanState.RemoteChanCfg.CsvDelay) remoteDelay := uint32(chanState.RemoteChanCfg.CsvDelay)
remotePkScript, err := commitScriptToSelf(remoteDelay, remoteDelayKey, remotePkScript, err := commitScriptToSelf(remoteDelay, keyRing.delayKey,
revocationKey) keyRing.revocationKey)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -1143,7 +1166,7 @@ func newBreachRetribution(chanState *channeldb.OpenChannel, stateNum uint64,
if err != nil { if err != nil {
return nil, err return nil, err
} }
localPkScript, err := commitScriptUnencumbered(localKey) localPkScript, err := commitScriptUnencumbered(keyRing.localKey)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -1180,13 +1203,8 @@ func newBreachRetribution(chanState *channeldb.OpenChannel, stateNum uint64,
// If the local balance exceeds the remote party's dust limit, // If the local balance exceeds the remote party's dust limit,
// instantiate the local sign descriptor. // instantiate the local sign descriptor.
if localAmt >= chanState.RemoteChanCfg.DustLimit { if localAmt >= chanState.RemoteChanCfg.DustLimit {
// We'll need to reconstruct the single tweak so we can sweep
// our non-delayed pay-to-self output self.
singleTweak := SingleTweakBytes(commitmentPoint,
chanState.LocalChanCfg.PaymentBasePoint)
localSignDesc = &SignDescriptor{ localSignDesc = &SignDescriptor{
SingleTweak: singleTweak, SingleTweak: keyRing.localKeyTweak,
PubKey: chanState.LocalChanCfg.PaymentBasePoint, PubKey: chanState.LocalChanCfg.PaymentBasePoint,
WitnessScript: localPkScript, WitnessScript: localPkScript,
Output: &wire.TxOut{ Output: &wire.TxOut{
@ -1226,8 +1244,8 @@ func newBreachRetribution(chanState *channeldb.OpenChannel, stateNum uint64,
// the sender of the HTLC (relative to us). So we'll // the sender of the HTLC (relative to us). So we'll
// re-generate the sender HTLC script. // re-generate the sender HTLC script.
if htlc.Incoming { if htlc.Incoming {
htlcScript, err = senderHTLCScript(localKey, remoteKey, htlcScript, err = senderHTLCScript(keyRing.localKey,
revocationKey, htlc.RHash[:]) keyRing.remoteKey, keyRing.revocationKey, htlc.RHash[:])
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -1237,8 +1255,8 @@ func newBreachRetribution(chanState *channeldb.OpenChannel, stateNum uint64,
// receiver of this HTLC. // receiver of this HTLC.
} else { } else {
htlcScript, err = receiverHTLCScript( htlcScript, err = receiverHTLCScript(
htlc.RefundTimeout, localKey, remoteKey, htlc.RefundTimeout, keyRing.localKey, keyRing.remoteKey,
revocationKey, htlc.RHash[:], keyRing.revocationKey, htlc.RHash[:],
) )
if err != nil { if err != nil {
return nil, err return nil, err
@ -1362,7 +1380,7 @@ func (lc *LightningChannel) closeObserver(channelCloseNtfn *chainntnfs.SpendEven
// necessary. // necessary.
// //
// We'll also handle the case of the remote party broadcasting their // We'll also handle the case of the remote party broadcasting their
// commitment transaction which is one height above ours. This case an // commitment transaction which is one height above ours. This case can
// arise when we initiate a state transition, but the remote party has // arise when we initiate a state transition, but the remote party has
// a fail crash _after_ accepting the new state, but _before_ sending // a fail crash _after_ accepting the new state, but _before_ sending
// their signature to us. // their signature to us.
@ -1394,17 +1412,15 @@ func (lc *LightningChannel) closeObserver(channelCloseNtfn *chainntnfs.SpendEven
// revocation point so we can re-construct the HTLC state and // revocation point so we can re-construct the HTLC state and
// also our payment key. // also our payment key.
commitPoint := lc.channelState.RemoteCurrentRevocation commitPoint := lc.channelState.RemoteCurrentRevocation
revokeKey := DeriveRevocationPubkey( keyRing := deriveCommitmentKeys(commitPoint, false,
lc.localChanCfg.RevocationBasePoint, lc.localChanCfg, lc.remoteChanCfg)
commitPoint,
)
// Next, we'll obtain HTLC resolutions for all the outgoing // Next, we'll obtain HTLC resolutions for all the outgoing
// HTLC's we had on their commitment transaction. // HTLC's we had on their commitment transaction.
htlcResolutions, localKey, err := extractHtlcResolutions( htlcResolutions, err := extractHtlcResolutions(
lc.channelState.FeePerKw, false, lc.signer, lc.channelState.FeePerKw, false, lc.signer,
lc.channelState.Htlcs, commitPoint, lc.channelState.Htlcs, keyRing,
revokeKey, lc.localChanCfg, lc.remoteChanCfg, lc.localChanCfg, lc.remoteChanCfg,
*commitSpend.SpenderTxHash) *commitSpend.SpenderTxHash)
if err != nil { if err != nil {
walletLog.Errorf("unable to create htlc "+ walletLog.Errorf("unable to create htlc "+
@ -1415,7 +1431,7 @@ func (lc *LightningChannel) closeObserver(channelCloseNtfn *chainntnfs.SpendEven
// Before we can generate the proper sign descriptor, we'll // Before we can generate the proper sign descriptor, we'll
// need to locate the output index of our non-delayed output on // need to locate the output index of our non-delayed output on
// the commitment transaction. // the commitment transaction.
selfP2WKH, err := commitScriptUnencumbered(localKey) selfP2WKH, err := commitScriptUnencumbered(keyRing.localKey)
if err != nil { if err != nil {
walletLog.Errorf("unable to create self commit "+ walletLog.Errorf("unable to create self commit "+
"script: %v", err) "script: %v", err)
@ -1440,7 +1456,7 @@ func (lc *LightningChannel) closeObserver(channelCloseNtfn *chainntnfs.SpendEven
localPayBase := lc.localChanCfg.PaymentBasePoint localPayBase := lc.localChanCfg.PaymentBasePoint
selfSignDesc = &SignDescriptor{ selfSignDesc = &SignDescriptor{
PubKey: localPayBase, PubKey: localPayBase,
SingleTweak: SingleTweakBytes(commitPoint, localPayBase), SingleTweak: keyRing.localKeyTweak,
WitnessScript: selfP2WKH, WitnessScript: selfP2WKH,
Output: &wire.TxOut{ Output: &wire.TxOut{
Value: int64(lc.channelState.LocalBalance.ToSatoshis()), Value: int64(lc.channelState.LocalBalance.ToSatoshis()),
@ -1583,27 +1599,12 @@ func (lc *LightningChannel) restoreStateLogs() error {
// the point derived from the commitment secret at the remote party's // the point derived from the commitment secret at the remote party's
// revocation based. // revocation based.
localCommitPoint := ComputeCommitmentPoint(ourRevPreImage[:]) localCommitPoint := ComputeCommitmentPoint(ourRevPreImage[:])
localRevocation := DeriveRevocationPubkey( localCommitKeys := deriveCommitmentKeys(localCommitPoint, true,
remoteChanCfg.RevocationBasePoint, localChanCfg, remoteChanCfg)
localCommitPoint,
)
remoteCommitPoint := lc.channelState.RemoteCurrentRevocation remoteCommitPoint := lc.channelState.RemoteCurrentRevocation
remoteRevocation := DeriveRevocationPubkey( remoteCommitKeys := deriveCommitmentKeys(remoteCommitPoint, false,
localChanCfg.RevocationBasePoint, localChanCfg, remoteChanCfg)
remoteCommitPoint,
)
// Additionally, we'll fetch the current payment base points which are
// required to fully generate the scripts.
localCommitLocalKey := TweakPubKey(localChanCfg.PaymentBasePoint,
localCommitPoint)
localCommitRemoteKey := TweakPubKey(remoteChanCfg.PaymentBasePoint,
localCommitPoint)
remoteCommitLocalKey := TweakPubKey(localChanCfg.PaymentBasePoint,
remoteCommitPoint)
remoteCommitRemoteKey := TweakPubKey(remoteChanCfg.PaymentBasePoint,
remoteCommitPoint)
var ourCounter, theirCounter uint64 var ourCounter, theirCounter uint64
@ -1637,8 +1638,8 @@ func (lc *LightningChannel) restoreStateLogs() error {
if !isDustLocal { if !isDustLocal {
ourP2WSH, ourWitnessScript, err = lc.genHtlcScript( ourP2WSH, ourWitnessScript, err = lc.genHtlcScript(
htlc.Incoming, true, htlc.RefundTimeout, htlc.RHash, htlc.Incoming, true, htlc.RefundTimeout, htlc.RHash,
localCommitLocalKey, localCommitRemoteKey, localCommitKeys.localKey, localCommitKeys.remoteKey,
localRevocation) localCommitKeys.revocationKey)
if err != nil { if err != nil {
return err return err
} }
@ -1646,8 +1647,8 @@ func (lc *LightningChannel) restoreStateLogs() error {
if !isDustRemote { if !isDustRemote {
theirP2WSH, theirWitnessScript, err = lc.genHtlcScript( theirP2WSH, theirWitnessScript, err = lc.genHtlcScript(
htlc.Incoming, false, htlc.RefundTimeout, htlc.RHash, htlc.Incoming, false, htlc.RefundTimeout, htlc.RHash,
remoteCommitLocalKey, remoteCommitRemoteKey, remoteCommitKeys.localKey, remoteCommitKeys.remoteKey,
remoteRevocation) remoteCommitKeys.revocationKey)
if err != nil { if err != nil {
return err return err
} }
@ -1735,11 +1736,9 @@ func (lc *LightningChannel) fetchHTLCView(theirLogIndex, ourLogIndex uint64) *ht
// both local and remote commitment transactions in order to sign or verify new // both local and remote commitment transactions in order to sign or verify new
// commitment updates. A fully populated commitment is returned which reflects // commitment updates. A fully populated commitment is returned which reflects
// the proper balances for both sides at this point in the commitment chain. // the proper balances for both sides at this point in the commitment chain.
//
// TODO(roasbeef): update commit to to have all keys?
func (lc *LightningChannel) fetchCommitmentView(remoteChain bool, func (lc *LightningChannel) fetchCommitmentView(remoteChain bool,
ourLogIndex, theirLogIndex uint64, ourLogIndex, theirLogIndex uint64,
commitPoint *btcec.PublicKey) (*commitment, error) { keyRing *commitmentKeyRing) (*commitment, error) {
commitChain := lc.localCommitChain commitChain := lc.localCommitChain
if remoteChain { if remoteChain {
@ -1853,9 +1852,8 @@ func (lc *LightningChannel) fetchCommitmentView(remoteChain bool,
} }
var ( var (
delayKey, paymentKey, revocationKey *btcec.PublicKey delay uint32
delay uint32 delayBalance, p2wkhBalance btcutil.Amount
delayBalance, p2wkhBalance btcutil.Amount
) )
// We'll now compute the delay, payment and revocation key based on the // We'll now compute the delay, payment and revocation key based on the
@ -1864,40 +1862,20 @@ func (lc *LightningChannel) fetchCommitmentView(remoteChain bool,
// revocation key, we take the opposite party's revocation base point // revocation key, we take the opposite party's revocation base point
// and combine that with the current commitment point. // and combine that with the current commitment point.
if remoteChain { if remoteChain {
delayKey = TweakPubKey(lc.remoteChanCfg.DelayBasePoint,
commitPoint)
paymentKey = TweakPubKey(lc.localChanCfg.PaymentBasePoint,
commitPoint)
revocationKey = DeriveRevocationPubkey(
lc.localChanCfg.RevocationBasePoint,
commitPoint,
)
delay = uint32(lc.remoteChanCfg.CsvDelay) delay = uint32(lc.remoteChanCfg.CsvDelay)
delayBalance = theirBalance.ToSatoshis() delayBalance = theirBalance.ToSatoshis()
p2wkhBalance = ourBalance.ToSatoshis() p2wkhBalance = ourBalance.ToSatoshis()
} else { } else {
delayKey = TweakPubKey(lc.localChanCfg.DelayBasePoint,
commitPoint)
paymentKey = TweakPubKey(lc.remoteChanCfg.PaymentBasePoint,
commitPoint)
revocationKey = DeriveRevocationPubkey(
lc.remoteChanCfg.RevocationBasePoint,
commitPoint,
)
delay = uint32(lc.localChanCfg.CsvDelay) delay = uint32(lc.localChanCfg.CsvDelay)
delayBalance = ourBalance.ToSatoshis() delayBalance = ourBalance.ToSatoshis()
p2wkhBalance = theirBalance.ToSatoshis() p2wkhBalance = theirBalance.ToSatoshis()
} }
// TODO(roasbeef); create all keys unconditionally within commitment
// store in commitment, will need all when doing HTLC's
// Generate a new commitment transaction with all the latest // Generate a new commitment transaction with all the latest
// unsettled/un-timed out HTLCs. // unsettled/un-timed out HTLCs.
commitTx, err := CreateCommitTx(lc.fundingTxIn, delayKey, paymentKey, commitTx, err := CreateCommitTx(lc.fundingTxIn, keyRing.delayKey,
revocationKey, delay, delayBalance, p2wkhBalance, dustLimit) keyRing.paymentKey, keyRing.revocationKey, delay, delayBalance,
p2wkhBalance, dustLimit)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -1906,17 +1884,14 @@ func (lc *LightningChannel) fetchCommitmentView(remoteChain bool,
// Each output includes an off-chain 2-of-2 covenant clause, so we'll // Each output includes an off-chain 2-of-2 covenant clause, so we'll
// need the objective local/remote keys for this particular commitment // need the objective local/remote keys for this particular commitment
// as well. // as well.
// TODO(roasbeef): could avoid computing them both here
localKey := TweakPubKey(lc.localChanCfg.PaymentBasePoint, commitPoint)
remoteKey := TweakPubKey(lc.remoteChanCfg.PaymentBasePoint, commitPoint)
for _, htlc := range filteredHTLCView.ourUpdates { for _, htlc := range filteredHTLCView.ourUpdates {
if htlcIsDust(false, !remoteChain, feePerKw, if htlcIsDust(false, !remoteChain, feePerKw,
htlc.Amount.ToSatoshis(), dustLimit) { htlc.Amount.ToSatoshis(), dustLimit) {
continue continue
} }
err := lc.addHTLC(commitTx, ourCommitTx, false, htlc, localKey, err := lc.addHTLC(commitTx, ourCommitTx, false, htlc, keyRing.localKey,
remoteKey, revocationKey) keyRing.remoteKey, keyRing.revocationKey)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -1927,8 +1902,8 @@ func (lc *LightningChannel) fetchCommitmentView(remoteChain bool,
continue continue
} }
err := lc.addHTLC(commitTx, ourCommitTx, true, htlc, localKey, err := lc.addHTLC(commitTx, ourCommitTx, true, htlc, keyRing.localKey,
remoteKey, revocationKey) keyRing.remoteKey, keyRing.revocationKey)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -2156,26 +2131,10 @@ func processRemoveEntry(htlc *PaymentDescriptor, ourBalance,
// generating a new commitment for the remote party. The jobs generated by the // generating a new commitment for the remote party. The jobs generated by the
// signature can be submitted to the sigPool to generate all the signatures // signature can be submitted to the sigPool to generate all the signatures
// asynchronously and in parallel. // asynchronously and in parallel.
// func genRemoteHtlcSigJobs(keyRing *commitmentKeyRing,
// TODO(roasbeef): all keys will eventually be generated within the commitment
// itself
func genRemoteHtlcSigJobs(commitPoint *btcec.PublicKey,
localChanCfg, remoteChanCfg *channeldb.ChannelConfig, localChanCfg, remoteChanCfg *channeldb.ChannelConfig,
remoteCommitView *commitment) ([]signJob, chan struct{}, error) { remoteCommitView *commitment) ([]signJob, chan struct{}, error) {
// First, we'll generate all the keys required to generate the scripts
// for each HTLC output and transaction.
//
// TODO(roabseef): avoid re-calculating, put in commitment struct?
commitTweak := SingleTweakBytes(commitPoint,
localChanCfg.PaymentBasePoint)
revocationKey := DeriveRevocationPubkey(
localChanCfg.RevocationBasePoint,
commitPoint,
)
remoteDelayKey := TweakPubKey(remoteChanCfg.DelayBasePoint,
commitPoint)
txHash := remoteCommitView.txn.TxHash() txHash := remoteCommitView.txn.TxHash()
dustLimit := localChanCfg.DustLimit dustLimit := localChanCfg.DustLimit
feePerKw := remoteCommitView.feePerKw feePerKw := remoteCommitView.feePerKw
@ -2221,7 +2180,7 @@ func genRemoteHtlcSigJobs(commitPoint *btcec.PublicKey,
} }
sigJob.tx, err = createHtlcTimeoutTx(op, outputAmt, sigJob.tx, err = createHtlcTimeoutTx(op, outputAmt,
htlc.Timeout, uint32(remoteChanCfg.CsvDelay), htlc.Timeout, uint32(remoteChanCfg.CsvDelay),
revocationKey, remoteDelayKey) keyRing.revocationKey, keyRing.delayKey)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
@ -2231,7 +2190,7 @@ func genRemoteHtlcSigJobs(commitPoint *btcec.PublicKey,
// transaction. Note we use the raw HTLC amount. // transaction. Note we use the raw HTLC amount.
sigJob.signDesc = SignDescriptor{ sigJob.signDesc = SignDescriptor{
PubKey: localChanCfg.PaymentBasePoint, PubKey: localChanCfg.PaymentBasePoint,
SingleTweak: commitTweak, SingleTweak: keyRing.localKeyTweak,
WitnessScript: htlc.theirWitnessScript, WitnessScript: htlc.theirWitnessScript,
Output: &wire.TxOut{ Output: &wire.TxOut{
Value: int64(htlc.Amount.ToSatoshis()), Value: int64(htlc.Amount.ToSatoshis()),
@ -2270,8 +2229,8 @@ func genRemoteHtlcSigJobs(commitPoint *btcec.PublicKey,
Index: uint32(htlc.remoteOutputIndex), Index: uint32(htlc.remoteOutputIndex),
} }
sigJob.tx, err = createHtlcSuccessTx(op, outputAmt, sigJob.tx, err = createHtlcSuccessTx(op, outputAmt,
uint32(remoteChanCfg.CsvDelay), revocationKey, uint32(remoteChanCfg.CsvDelay), keyRing.revocationKey,
remoteDelayKey) keyRing.delayKey)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
@ -2281,7 +2240,7 @@ func genRemoteHtlcSigJobs(commitPoint *btcec.PublicKey,
// transaction. Note we use the raw HTLC amount. // transaction. Note we use the raw HTLC amount.
sigJob.signDesc = SignDescriptor{ sigJob.signDesc = SignDescriptor{
PubKey: localChanCfg.PaymentBasePoint, PubKey: localChanCfg.PaymentBasePoint,
SingleTweak: commitTweak, SingleTweak: keyRing.localKeyTweak,
WitnessScript: htlc.theirWitnessScript, WitnessScript: htlc.theirWitnessScript,
Output: &wire.TxOut{ Output: &wire.TxOut{
Value: int64(htlc.Amount.ToSatoshis()), Value: int64(htlc.Amount.ToSatoshis()),
@ -2333,6 +2292,8 @@ func (lc *LightningChannel) SignNextCommitment() (*btcec.Signature, []*btcec.Sig
// used within fetchCommitmentView to derive all the keys necessary to // used within fetchCommitmentView to derive all the keys necessary to
// construct the commitment state. // construct the commitment state.
commitPoint := lc.channelState.RemoteNextRevocation commitPoint := lc.channelState.RemoteNextRevocation
keyRing := deriveCommitmentKeys(commitPoint, false, lc.localChanCfg,
lc.remoteChanCfg)
// Create a new commitment view which will calculate the evaluated // Create a new commitment view which will calculate the evaluated
// state of the remote node's new commitment including our latest added // state of the remote node's new commitment including our latest added
@ -2342,8 +2303,7 @@ func (lc *LightningChannel) SignNextCommitment() (*btcec.Signature, []*btcec.Sig
// _all_ of our changes (pending or committed) but only the remote // _all_ of our changes (pending or committed) but only the remote
// node's changes up to the last change we've ACK'd. // node's changes up to the last change we've ACK'd.
newCommitView, err := lc.fetchCommitmentView(true, newCommitView, err := lc.fetchCommitmentView(true,
lc.localUpdateLog.logIndex, lc.remoteUpdateLog.ackedIndex, lc.localUpdateLog.logIndex, lc.remoteUpdateLog.ackedIndex, keyRing)
commitPoint)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
@ -2364,7 +2324,7 @@ func (lc *LightningChannel) SignNextCommitment() (*btcec.Signature, []*btcec.Sig
// need to generate signatures of each of them for the remote party's // 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 // commitment state. We do so in two phases: first we generate and
// submit the set of signature jobs to the worker pool. // submit the set of signature jobs to the worker pool.
sigBatch, cancelChan, err := genRemoteHtlcSigJobs(commitPoint, sigBatch, cancelChan, err := genRemoteHtlcSigJobs(keyRing,
lc.localChanCfg, lc.remoteChanCfg, newCommitView, lc.localChanCfg, lc.remoteChanCfg, newCommitView,
) )
if err != nil { if err != nil {
@ -2507,7 +2467,7 @@ func (lc *LightningChannel) validateCommitmentSanity(theirLogCounter,
// commitment state. The jobs generated are fully populated, and can be sent // commitment state. The jobs generated are fully populated, and can be sent
// directly into the pool of workers. // directly into the pool of workers.
func genHtlcSigValidationJobs(localCommitmentView *commitment, func genHtlcSigValidationJobs(localCommitmentView *commitment,
commitPoint *btcec.PublicKey, htlcSigs []*btcec.Signature, keyRing *commitmentKeyRing, htlcSigs []*btcec.Signature,
localChanCfg, remoteChanCfg *channeldb.ChannelConfig) []verifyJob { localChanCfg, remoteChanCfg *channeldb.ChannelConfig) []verifyJob {
// If this new commitment state doesn't have any HTLC's that are to be // If this new commitment state doesn't have any HTLC's that are to be
@ -2516,16 +2476,6 @@ func genHtlcSigValidationJobs(localCommitmentView *commitment,
return nil return nil
} }
// First, we'll re-derive the keys necessary to reconstruct the HTLC
// output and transaction state.
remoteKey := TweakPubKey(remoteChanCfg.PaymentBasePoint, commitPoint)
revocationKey := DeriveRevocationPubkey(
remoteChanCfg.RevocationBasePoint,
commitPoint,
)
localDelayKey := TweakPubKey(localChanCfg.DelayBasePoint,
commitPoint)
txHash := localCommitmentView.txn.TxHash() txHash := localCommitmentView.txn.TxHash()
feePerKw := localCommitmentView.feePerKw feePerKw := localCommitmentView.feePerKw
@ -2565,7 +2515,7 @@ func genHtlcSigValidationJobs(localCommitmentView *commitment,
successTx, err := createHtlcSuccessTx(op, successTx, err := createHtlcSuccessTx(op,
outputAmt, uint32(localChanCfg.CsvDelay), outputAmt, uint32(localChanCfg.CsvDelay),
revocationKey, localDelayKey) keyRing.revocationKey, keyRing.delayKey)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -2606,7 +2556,7 @@ func genHtlcSigValidationJobs(localCommitmentView *commitment,
timeoutTx, err := createHtlcTimeoutTx(op, timeoutTx, err := createHtlcTimeoutTx(op,
outputAmt, htlc.Timeout, outputAmt, htlc.Timeout,
uint32(localChanCfg.CsvDelay), uint32(localChanCfg.CsvDelay),
revocationKey, localDelayKey, keyRing.revocationKey, keyRing.delayKey,
) )
if err != nil { if err != nil {
return nil, err return nil, err
@ -2635,7 +2585,7 @@ func genHtlcSigValidationJobs(localCommitmentView *commitment,
} }
verifyJobs = append(verifyJobs, verifyJob{ verifyJobs = append(verifyJobs, verifyJob{
pubKey: remoteKey, pubKey: keyRing.remoteKey,
sig: htlcSigs[i], sig: htlcSigs[i],
sigHash: sigHash, sigHash: sigHash,
}) })
@ -2679,13 +2629,14 @@ func (lc *LightningChannel) ReceiveNewCommitment(commitSig *btcec.Signature,
return err return err
} }
commitPoint := ComputeCommitmentPoint(commitSecret[:]) commitPoint := ComputeCommitmentPoint(commitSecret[:])
keyRing := deriveCommitmentKeys(commitPoint, true, lc.localChanCfg,
lc.remoteChanCfg)
// With the current commitment point re-calculated, construct the new // With the current commitment point re-calculated, construct the new
// commitment view which includes all the entries we know of in their // commitment view which includes all the entries we know of in their
// HTLC log, and up to ourLogIndex in our HTLC log. // HTLC log, and up to ourLogIndex in our HTLC log.
localCommitmentView, err := lc.fetchCommitmentView(false, localCommitmentView, err := lc.fetchCommitmentView(false,
lc.localUpdateLog.ackedIndex, lc.remoteUpdateLog.logIndex, lc.localUpdateLog.ackedIndex, lc.remoteUpdateLog.logIndex, keyRing)
commitPoint)
if err != nil { if err != nil {
return err return err
} }
@ -2719,8 +2670,7 @@ func (lc *LightningChannel) ReceiveNewCommitment(commitSig *btcec.Signature,
// pool to verify each of the HTLc signatures presented. Once // pool to verify each of the HTLc signatures presented. Once
// generated, we'll submit these jobs to the worker pool. // generated, we'll submit these jobs to the worker pool.
verifyJobs := genHtlcSigValidationJobs(localCommitmentView, verifyJobs := genHtlcSigValidationJobs(localCommitmentView,
commitPoint, keyRing, htlcSigs, lc.localChanCfg, lc.remoteChanCfg)
htlcSigs, lc.localChanCfg, lc.remoteChanCfg)
cancelChan := make(chan struct{}) cancelChan := make(chan struct{})
verifyResps := lc.sigPool.SubmitVerifyBatch(verifyJobs, cancelChan) verifyResps := lc.sigPool.SubmitVerifyBatch(verifyJobs, cancelChan)
@ -3413,9 +3363,9 @@ type OutgoingHtlcResolution struct {
// caller to sweep an outgoing HTLC present on either their, or the remote // caller to sweep an outgoing HTLC present on either their, or the remote
// party's commitment transaction. // party's commitment transaction.
func newHtlcResolution(signer Signer, localChanCfg *channeldb.ChannelConfig, func newHtlcResolution(signer Signer, localChanCfg *channeldb.ChannelConfig,
commitHash chainhash.Hash, htlc *channeldb.HTLC, commitPoint, commitHash chainhash.Hash, htlc *channeldb.HTLC, keyRing *commitmentKeyRing,
delayKey, localKey, remoteKey *btcec.PublicKey, revokeKey *btcec.PublicKey, feePewKw, dustLimit btcutil.Amount, csvDelay uint32,
feePewKw, dustLimit btcutil.Amount, csvDelay uint32) (*OutgoingHtlcResolution, error) { ) (*OutgoingHtlcResolution, error) {
op := wire.OutPoint{ op := wire.OutPoint{
Hash: commitHash, Hash: commitHash,
@ -3432,7 +3382,7 @@ func newHtlcResolution(signer Signer, localChanCfg *channeldb.ChannelConfig,
// transaction. // transaction.
timeoutTx, err := createHtlcTimeoutTx( timeoutTx, err := createHtlcTimeoutTx(
op, secondLevelOutputAmt, htlc.RefundTimeout, csvDelay, op, secondLevelOutputAmt, htlc.RefundTimeout, csvDelay,
revokeKey, delayKey, keyRing.revocationKey, keyRing.delayKey,
) )
if err != nil { if err != nil {
return nil, err return nil, err
@ -3441,16 +3391,14 @@ func newHtlcResolution(signer Signer, localChanCfg *channeldb.ChannelConfig,
// With the transaction created, we can generate a sign descriptor // With the transaction created, we can generate a sign descriptor
// that's capable of generating the signature required to spend the // that's capable of generating the signature required to spend the
// HTLC output using the timeout transaction. // HTLC output using the timeout transaction.
htlcCreationScript, err := senderHTLCScript(localKey, remoteKey, htlcCreationScript, err := senderHTLCScript(keyRing.localKey,
revokeKey, htlc.RHash[:]) keyRing.remoteKey, keyRing.revocationKey, htlc.RHash[:])
if err != nil { if err != nil {
return nil, err return nil, err
} }
timeoutTweak := SingleTweakBytes(commitPoint,
localChanCfg.PaymentBasePoint)
timeoutSignDesc := SignDescriptor{ timeoutSignDesc := SignDescriptor{
PubKey: localChanCfg.PaymentBasePoint, PubKey: localChanCfg.PaymentBasePoint,
SingleTweak: timeoutTweak, SingleTweak: keyRing.localKeyTweak,
WitnessScript: htlcCreationScript, WitnessScript: htlcCreationScript,
Output: &wire.TxOut{ Output: &wire.TxOut{
Value: int64(htlc.Amt.ToSatoshis()), Value: int64(htlc.Amt.ToSatoshis()),
@ -3473,7 +3421,7 @@ func newHtlcResolution(signer Signer, localChanCfg *channeldb.ChannelConfig,
// transaction creates so we can generate the signDesc required to // transaction creates so we can generate the signDesc required to
// complete the claim process after a delay period. // complete the claim process after a delay period.
htlcSweepScript, err := secondLevelHtlcScript( htlcSweepScript, err := secondLevelHtlcScript(
revokeKey, delayKey, csvDelay, keyRing.revocationKey, keyRing.delayKey, csvDelay,
) )
if err != nil { if err != nil {
return nil, err return nil, err
@ -3483,14 +3431,15 @@ func newHtlcResolution(signer Signer, localChanCfg *channeldb.ChannelConfig,
return nil, err return nil, err
} }
delayTweak := SingleTweakBytes(commitPoint, // TODO: Signing with the delay key is wrong for remote commitments
localDelayTweak := SingleTweakBytes(keyRing.commitPoint,
localChanCfg.DelayBasePoint) localChanCfg.DelayBasePoint)
return &OutgoingHtlcResolution{ return &OutgoingHtlcResolution{
Expiry: htlc.RefundTimeout, Expiry: htlc.RefundTimeout,
SignedTimeoutTx: timeoutTx, SignedTimeoutTx: timeoutTx,
SweepSignDesc: SignDescriptor{ SweepSignDesc: SignDescriptor{
PubKey: localChanCfg.DelayBasePoint, PubKey: localChanCfg.DelayBasePoint,
SingleTweak: delayTweak, SingleTweak: localDelayTweak,
WitnessScript: htlcSweepScript, WitnessScript: htlcSweepScript,
Output: &wire.TxOut{ Output: &wire.TxOut{
PkScript: htlcScriptHash, PkScript: htlcScriptHash,
@ -3505,17 +3454,9 @@ func newHtlcResolution(signer Signer, localChanCfg *channeldb.ChannelConfig,
// the local key used when generating the HTLC scrips. This function is to be // the local key used when generating the HTLC scrips. This function is to be
// used in two cases: force close, or a unilateral close. // used in two cases: force close, or a unilateral close.
func extractHtlcResolutions(feePerKw btcutil.Amount, ourCommit bool, func extractHtlcResolutions(feePerKw btcutil.Amount, ourCommit bool,
signer Signer, htlcs []*channeldb.HTLC, signer Signer, htlcs []*channeldb.HTLC, keyRing *commitmentKeyRing,
commitPoint, revokeKey *btcec.PublicKey,
localChanCfg, remoteChanCfg *channeldb.ChannelConfig, localChanCfg, remoteChanCfg *channeldb.ChannelConfig,
commitHash chainhash.Hash) ([]OutgoingHtlcResolution, *btcec.PublicKey, error) { commitHash chainhash.Hash) ([]OutgoingHtlcResolution, error) {
// As uusal, we start by re-generating the key-ring required to
// reconstruct the pkScripts used, and sign any transactions or inputs
// required to sweep all funds.
localKey := TweakPubKey(localChanCfg.PaymentBasePoint, commitPoint)
delayKey := TweakPubKey(localChanCfg.DelayBasePoint, commitPoint)
remoteKey := TweakPubKey(remoteChanCfg.PaymentBasePoint, commitPoint)
dustLimit := remoteChanCfg.DustLimit dustLimit := remoteChanCfg.DustLimit
csvDelay := remoteChanCfg.CsvDelay csvDelay := remoteChanCfg.CsvDelay
@ -3542,19 +3483,18 @@ func extractHtlcResolutions(feePerKw btcutil.Amount, ourCommit bool,
} }
ohr, err := newHtlcResolution( ohr, err := newHtlcResolution(
signer, localChanCfg, commitHash, htlc, commitPoint, signer, localChanCfg, commitHash, htlc, keyRing,
delayKey, localKey, remoteKey, revokeKey, feePerKw, feePerKw, dustLimit, uint32(csvDelay),
dustLimit, uint32(csvDelay),
) )
if err != nil { if err != nil {
return nil, nil, err return nil, err
} }
// TODO(roasbeef): needs to point to proper amount including // TODO(roasbeef): needs to point to proper amount including
htlcResolutions = append(htlcResolutions, *ohr) htlcResolutions = append(htlcResolutions, *ohr)
} }
return htlcResolutions, localKey, nil return htlcResolutions, nil
} }
// ForceCloseSummary describes the final commitment state before the channel is // ForceCloseSummary describes the final commitment state before the channel is
@ -3628,12 +3568,10 @@ func (lc *LightningChannel) ForceClose() (*ForceCloseSummary, error) {
return nil, err return nil, err
} }
commitPoint := ComputeCommitmentPoint(unusedRevocation[:]) commitPoint := ComputeCommitmentPoint(unusedRevocation[:])
revokeKey := DeriveRevocationPubkey( keyRing := deriveCommitmentKeys(commitPoint, true, lc.localChanCfg,
lc.remoteChanCfg.RevocationBasePoint, lc.remoteChanCfg)
commitPoint, selfScript, err := commitScriptToSelf(csvTimeout, keyRing.delayKey,
) keyRing.revocationKey)
delayKey := TweakPubKey(lc.localChanCfg.DelayBasePoint, commitPoint)
selfScript, err := commitScriptToSelf(csvTimeout, delayKey, revokeKey)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -3685,9 +3623,9 @@ func (lc *LightningChannel) ForceClose() (*ForceCloseSummary, error) {
// need to create a series of sign descriptors for any lingering // need to create a series of sign descriptors for any lingering
// outgoing HTLC's that we'll need to claim as well. // outgoing HTLC's that we'll need to claim as well.
txHash := commitTx.TxHash() txHash := commitTx.TxHash()
htlcResolutions, _, err := extractHtlcResolutions( htlcResolutions, err := extractHtlcResolutions(
lc.channelState.FeePerKw, true, lc.signer, lc.channelState.Htlcs, lc.channelState.FeePerKw, true, lc.signer, lc.channelState.Htlcs,
commitPoint, revokeKey, lc.localChanCfg, lc.remoteChanCfg, txHash) keyRing, lc.localChanCfg, lc.remoteChanCfg, txHash)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -4009,3 +3947,38 @@ func (lc *LightningChannel) RemoteNextRevocation() *btcec.PublicKey {
return lc.channelState.RemoteNextRevocation return lc.channelState.RemoteNextRevocation
} }
// deriveCommitmentKey generates a new commitment key set using the base points
// and commitment point. The keys are derived differently depending whether the
// commitment transaction is ours or the remote peer's.
func deriveCommitmentKeys(commitPoint *btcec.PublicKey, isOurCommit bool,
localChanCfg, remoteChanCfg *channeldb.ChannelConfig) *commitmentKeyRing {
keyRing := new(commitmentKeyRing)
keyRing.commitPoint = commitPoint
keyRing.localKeyTweak = SingleTweakBytes(commitPoint,
localChanCfg.PaymentBasePoint)
keyRing.localKey = TweakPubKeyWithTweak(localChanCfg.PaymentBasePoint,
keyRing.localKeyTweak)
keyRing.remoteKey = TweakPubKey(remoteChanCfg.PaymentBasePoint, commitPoint)
// We'll now compute the delay, payment and revocation key based on the
// current commitment point. All keys are tweaked each state in order
// to ensure the keys from each state are unlinkable. TO create the
// revocation key, we take the opposite party's revocation base point
// and combine that with the current commitment point.
var delayBasePoint, revocationBasePoint *btcec.PublicKey
if isOurCommit {
keyRing.paymentKey = keyRing.remoteKey
delayBasePoint = localChanCfg.DelayBasePoint
revocationBasePoint = remoteChanCfg.RevocationBasePoint
} else {
keyRing.paymentKey = keyRing.localKey
delayBasePoint = remoteChanCfg.DelayBasePoint
revocationBasePoint = localChanCfg.RevocationBasePoint
}
keyRing.delayKey = TweakPubKey(delayBasePoint, commitPoint)
keyRing.revocationKey = DeriveRevocationPubkey(revocationBasePoint, commitPoint)
return keyRing
}