Merge pull request #3821 from halseth/pluggable-anchors-lnwallet

[anchor] pluggable anchor commitments
This commit is contained in:
Olaoluwa Osuntokun 2020-03-09 19:49:05 -07:00 committed by GitHub
commit 3dda93e30d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
37 changed files with 1848 additions and 604 deletions

@ -896,6 +896,13 @@ func (bo *breachedOutput) CraftInputScript(signer input.Signer, txn *wire.MsgTx,
// must be built on top of the confirmation height before the output can be
// spent.
func (bo *breachedOutput) BlocksToMaturity() uint32 {
// If the output is a to_remote output we can claim, and it's of the
// confirmed type, we must wait one block before claiming it.
if bo.witnessType == input.CommitmentToRemoteConfirmed {
return 1
}
// All other breached outputs have no CSV delay.
return 0
}
@ -952,6 +959,12 @@ func newRetributionInfo(chanPoint *wire.OutPoint,
witnessType = input.CommitSpendNoDelayTweakless
}
// If the local delay is non-zero, it means this output is of
// the confirmed to_remote type.
if breachInfo.LocalDelay != 0 {
witnessType = input.CommitmentToRemoteConfirmed
}
localOutput := makeBreachedOutput(
&breachInfo.LocalOutpoint,
witnessType,
@ -1117,6 +1130,7 @@ func (b *breachArbiter) sweepSpendableOutputsTxn(txWeight int64,
for _, input := range inputs {
txn.AddTxIn(&wire.TxIn{
PreviousOutPoint: *input.OutPoint(),
Sequence: input.BlocksToMaturity(),
})
}

@ -62,6 +62,12 @@ func FetchBackupForChan(chanPoint wire.OutPoint,
return nil, fmt.Errorf("unable to find target channel")
}
// TODO(halseth): support chan backups for anchor types.
if targetChan.ChanType.HasAnchors() {
return nil, fmt.Errorf("channel type does not support " +
"backups yet")
}
// Once we have the target channel, we can assemble the backup using
// the source to obtain any extra information that we may need.
staticChanBackup, err := assembleChanBackup(chanSource, targetChan)
@ -85,14 +91,19 @@ func FetchStaticChanBackups(chanSource LiveChannelSource) ([]Single, error) {
// Now that we have all the channels, we'll use the chanSource to
// obtain any auxiliary information we need to craft a backup for each
// channel.
staticChanBackups := make([]Single, len(openChans))
for i, openChan := range openChans {
staticChanBackups := make([]Single, 0, len(openChans))
for _, openChan := range openChans {
// TODO(halseth): support chan backups for anchor types.
if openChan.ChanType.HasAnchors() {
continue
}
chanBackup, err := assembleChanBackup(chanSource, openChan)
if err != nil {
return nil, err
}
staticChanBackups[i] = *chanBackup
staticChanBackups = append(staticChanBackups, *chanBackup)
}
return staticChanBackups, nil

@ -213,6 +213,12 @@ func (s *SubSwapper) backupUpdater() {
// For all new open channels, we'll create a new SCB
// given the required information.
for _, newChan := range chanUpdate.NewChans {
// TODO(halseth): support chan backups for
// anchor types.
if newChan.ChanType.HasAnchors() {
continue
}
log.Debugf("Adding channel %v to backup state",
newChan.FundingOutpoint)

@ -176,6 +176,12 @@ const (
// disk. This bit may be on if the funding transaction was crafted by a
// wallet external to the primary daemon.
NoFundingTxBit ChannelType = 1 << 2
// AnchorOutputsBit indicates that the channel makes use of anchor
// outputs to bump the commitment transaction's effective feerate. This
// channel type also uses a delayed to_remote output script. If bit is
// set, we'll find the size of the anchor outputs in the database.
AnchorOutputsBit ChannelType = 1 << 3
)
// IsSingleFunder returns true if the channel type if one of the known single
@ -201,6 +207,12 @@ func (c ChannelType) HasFundingTx() bool {
return c&NoFundingTxBit == 0
}
// HasAnchors returns true if this channel type has anchor ouputs on its
// commitment.
func (c ChannelType) HasAnchors() bool {
return c&AnchorOutputsBit == AnchorOutputsBit
}
// ChannelConstraints represents a set of constraints meant to allow a node to
// limit their exposure, enact flow control and ensure that all HTLCs are
// economically relevant. This struct will be mirrored for both sides of the
@ -326,13 +338,15 @@ type ChannelCommitment struct {
// LocalBalance is the current available settled balance within the
// channel directly spendable by us.
//
// NOTE: This is the balance *after* subtracting any commitment fee.
// NOTE: This is the balance *after* subtracting any commitment fee,
// AND anchor output values.
LocalBalance lnwire.MilliSatoshi
// RemoteBalance is the current available settled balance within the
// channel directly spendable by the remote node.
//
// NOTE: This is the balance *after* subtracting any commitment fee.
// NOTE: This is the balance *after* subtracting any commitment fee,
// AND anchor output values.
RemoteBalance lnwire.MilliSatoshi
// CommitFee is the amount calculated to be paid in fees for the

@ -345,7 +345,7 @@ type config struct {
Watchtower *lncfg.Watchtower `group:"watchtower" namespace:"watchtower"`
LegacyProtocol *lncfg.LegacyProtocol `group:"legacyprotocol" namespace:"legacyprotocol"`
ProtocolOptions *lncfg.ProtocolOptions `group:"protocol" namespace:"protocol"`
AllowCircularRoute bool `long:"allow-circular-route" description:"If true, our node will allow htlc forwards that arrive and depart on the same channel."`
}

@ -351,9 +351,8 @@ func isOurCommitment(localChanCfg, remoteChanCfg channeldb.ChannelConfig,
// With the keys derived, we'll construct the remote script that'll be
// present if they have a non-dust balance on the commitment.
remoteDelay := uint32(remoteChanCfg.CsvDelay)
remoteScript, err := lnwallet.CommitScriptToRemote(
chanType, remoteDelay, commitKeyRing.ToRemoteKey,
remoteScript, _, err := lnwallet.CommitScriptToRemote(
chanType, commitKeyRing.ToRemoteKey,
)
if err != nil {
return false, err

@ -6,6 +6,7 @@ import (
"io"
"sync"
"github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btcutil"
"github.com/lightningnetwork/lnd/input"
@ -172,24 +173,45 @@ func (c *commitSweepResolver) Resolve() (ContractResolver, error) {
}
}
// We're dealing with our commitment transaction if the delay on the
// resolution isn't zero.
isLocalCommitTx := c.commitResolution.MaturityDelay != 0
// The output is on our local commitment if the script starts with
// OP_IF for the revocation clause. On the remote commitment it will
// either be a regular P2WKH or a simple sig spend with a CSV delay.
isLocalCommitTx := c.commitResolution.SelfOutputSignDesc.WitnessScript[0] == txscript.OP_IF
isDelayedOutput := c.commitResolution.MaturityDelay != 0
// There're two types of commitments, those that have tweaks
// for the remote key (us in this case), and those that don't.
// We'll rely on the presence of the commitment tweak to to
// discern which type of commitment this is.
c.log.Debugf("isDelayedOutput=%v, isLocalCommitTx=%v", isDelayedOutput,
isLocalCommitTx)
// There're three types of commitments, those that have tweaks
// for the remote key (us in this case), those that don't, and a third
// where there is no tweak and the output is delayed. On the local
// commitment our output will always be delayed. We'll rely on the
// presence of the commitment tweak to to discern which type of
// commitment this is.
var witnessType input.WitnessType
switch {
// Delayed output to us on our local commitment.
case isLocalCommitTx:
witnessType = input.CommitmentTimeLock
// A confirmed output to us on the remote commitment.
case isDelayedOutput:
witnessType = input.CommitmentToRemoteConfirmed
// A non-delayed output on the remote commitment where the key is
// tweakless.
case c.commitResolution.SelfOutputSignDesc.SingleTweak == nil:
witnessType = input.CommitSpendNoDelayTweakless
// A non-delayed output on the remote commitment where the key is
// tweaked.
default:
witnessType = input.CommitmentNoDelay
}
c.log.Infof("Sweeping with witness type: %v", witnessType)
// We'll craft an input with all the information required for
// the sweeper to create a fully valid sweeping transaction to
// recover these coins.

@ -133,6 +133,7 @@ func TestCommitSweepResolverNoDelay(t *testing.T) {
Output: &wire.TxOut{
Value: 100,
},
WitnessScript: []byte{0},
},
}
@ -162,6 +163,7 @@ func TestCommitSweepResolverDelay(t *testing.T) {
Output: &wire.TxOut{
Value: amt,
},
WitnessScript: []byte{0},
},
MaturityDelay: 3,
SelfOutPoint: outpoint,

@ -118,6 +118,7 @@ func (h *htlcSuccessResolver) Resolve() (ContractResolver, error) {
&h.htlcResolution.SweepSignDesc,
h.htlcResolution.Preimage[:],
h.broadcastHeight,
h.htlcResolution.CsvDelay,
)
// With the input created, we can now generate the full

@ -7,6 +7,7 @@ import (
"testing"
"time"
"github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcd/wire"
"github.com/lightningnetwork/lnd/chainntnfs"
"github.com/lightningnetwork/lnd/input"
@ -144,7 +145,8 @@ func TestHtlcTimeoutResolver(t *testing.T) {
timeout: true,
txToBroadcast: func() (*wire.MsgTx, error) {
witness, err := input.SenderHtlcSpendTimeout(
nil, signer, fakeSignDesc, sweepTx,
nil, txscript.SigHashAll, signer,
fakeSignDesc, sweepTx,
)
if err != nil {
return nil, err
@ -163,7 +165,8 @@ func TestHtlcTimeoutResolver(t *testing.T) {
timeout: false,
txToBroadcast: func() (*wire.MsgTx, error) {
witness, err := input.ReceiverHtlcSpendRedeem(
nil, fakePreimageBytes, signer,
nil, txscript.SigHashAll,
fakePreimageBytes, signer,
fakeSignDesc, sweepTx,
)
if err != nil {

@ -43,4 +43,8 @@ var defaultSetDesc = setDesc{
SetNodeAnn: {}, // N
SetInvoice: {}, // 9
},
lnwire.AnchorsOptional: {
SetInit: {}, // I
SetNodeAnn: {}, // N
},
}

@ -55,6 +55,9 @@ var deps = depDesc{
lnwire.MPPOptional: {
lnwire.PaymentAddrOptional: {},
},
lnwire.AnchorsOptional: {
lnwire.StaticRemoteKeyOptional: {},
},
}
// ValidateDeps asserts that a feature vector sets all features and their

@ -17,6 +17,9 @@ type Config struct {
// NoStaticRemoteKey unsets any optional or required StaticRemoteKey
// bits from all feature sets.
NoStaticRemoteKey bool
// NoAnchors unsets any bits signaling support for anchor outputs.
NoAnchors bool
}
// Manager is responsible for generating feature vectors for different requested
@ -76,6 +79,10 @@ func newManager(cfg Config, desc setDesc) (*Manager, error) {
raw.Unset(lnwire.StaticRemoteKeyOptional)
raw.Unset(lnwire.StaticRemoteKeyRequired)
}
if cfg.NoAnchors {
raw.Unset(lnwire.AnchorsOptional)
raw.Unset(lnwire.AnchorsRequired)
}
// Ensure that all of our feature sets properly set any
// dependent features.

@ -1106,6 +1106,42 @@ func (f *fundingManager) processFundingOpen(msg *lnwire.OpenChannel,
}
}
// commitmentType returns the commitment type to use for the channel, based on
// the features the two peers have available.
func commitmentType(localFeatures,
remoteFeatures *lnwire.FeatureVector) lnwallet.CommitmentType {
// If both peers are signalling support for anchor commitments, this
// implicitly mean we'll create the channel of this type. Note that
// this also enables tweakless commitments, as anchor commitments are
// always tweakless.
localAnchors := localFeatures.HasFeature(
lnwire.AnchorsOptional,
)
remoteAnchors := remoteFeatures.HasFeature(
lnwire.AnchorsOptional,
)
if localAnchors && remoteAnchors {
return lnwallet.CommitmentTypeAnchors
}
localTweakless := localFeatures.HasFeature(
lnwire.StaticRemoteKeyOptional,
)
remoteTweakless := remoteFeatures.HasFeature(
lnwire.StaticRemoteKeyOptional,
)
// If both nodes are signaling the proper feature bit for tweakless
// copmmitments, we'll use that.
if localTweakless && remoteTweakless {
return lnwallet.CommitmentTypeTweakless
}
// Otherwise we'll fall back to the legacy type.
return lnwallet.CommitmentTypeLegacy
}
// handleFundingOpen creates an initial 'ChannelReservation' within the wallet,
// then responds to the source peer with an accept channel message progressing
// the funding workflow.
@ -1228,13 +1264,9 @@ func (f *fundingManager) handleFundingOpen(fmsg *fundingOpenMsg) {
// negotiated the new tweakless commitment format. This is only the
// case if *both* us and the remote peer are signaling the proper
// feature bit.
localTweakless := fmsg.peer.LocalFeatures().HasFeature(
lnwire.StaticRemoteKeyOptional,
commitType := commitmentType(
fmsg.peer.LocalFeatures(), fmsg.peer.RemoteFeatures(),
)
remoteTweakless := fmsg.peer.RemoteFeatures().HasFeature(
lnwire.StaticRemoteKeyOptional,
)
tweaklessCommitment := localTweakless && remoteTweakless
chainHash := chainhash.Hash(msg.ChainHash)
req := &lnwallet.InitFundingReserveMsg{
ChainHash: &chainHash,
@ -1248,7 +1280,7 @@ func (f *fundingManager) handleFundingOpen(fmsg *fundingOpenMsg) {
PushMSat: msg.PushAmount,
Flags: msg.ChannelFlags,
MinConfs: 1,
Tweakless: tweaklessCommitment,
CommitType: commitType,
}
reservation, err := f.cfg.Wallet.InitChannelReservation(req)
@ -1307,9 +1339,9 @@ func (f *fundingManager) handleFundingOpen(fmsg *fundingOpenMsg) {
reservation.SetOurUpfrontShutdown(shutdown)
fndgLog.Infof("Requiring %v confirmations for pendingChan(%x): "+
"amt=%v, push_amt=%v, tweakless=%v, upfrontShutdown=%x", numConfsReq,
"amt=%v, push_amt=%v, committype=%v, upfrontShutdown=%x", numConfsReq,
fmsg.msg.PendingChannelID, amt, msg.PushAmount,
tweaklessCommitment, msg.UpfrontShutdownScript)
commitType, msg.UpfrontShutdownScript)
// Generate our required constraints for the remote party.
remoteCsvDelay := f.cfg.RequiredRemoteDelay(amt)
@ -2904,13 +2936,9 @@ func (f *fundingManager) handleInitFundingMsg(msg *initFundingMsg) {
// negotiated the new tweakless commitment format. This is only the
// case if *both* us and the remote peer are signaling the proper
// feature bit.
localTweakless := msg.peer.LocalFeatures().HasFeature(
lnwire.StaticRemoteKeyOptional,
commitType := commitmentType(
msg.peer.LocalFeatures(), msg.peer.RemoteFeatures(),
)
remoteTweakless := msg.peer.RemoteFeatures().HasFeature(
lnwire.StaticRemoteKeyOptional,
)
tweaklessCommitment := localTweakless && remoteTweakless
req := &lnwallet.InitFundingReserveMsg{
ChainHash: &msg.chainHash,
PendingChanID: chanID,
@ -2924,7 +2952,7 @@ func (f *fundingManager) handleInitFundingMsg(msg *initFundingMsg) {
PushMSat: msg.pushAmt,
Flags: channelFlags,
MinConfs: msg.minConfs,
Tweakless: tweaklessCommitment,
CommitType: commitType,
ChanFunder: msg.chanFunder,
}
@ -3012,7 +3040,7 @@ func (f *fundingManager) handleInitFundingMsg(msg *initFundingMsg) {
maxHtlcs := f.cfg.RequiredRemoteMaxHTLCs(capacity)
fndgLog.Infof("Starting funding workflow with %v for pending_id(%x), "+
"tweakless=%v", msg.peer.Address(), chanID, tweaklessCommitment)
"committype=%v", msg.peer.Address(), chanID, commitType)
fundingOpen := lnwire.OpenChannel{
ChainHash: *f.cfg.Wallet.Cfg.NetParams.GenesisHash,

@ -419,7 +419,12 @@ func (l *channelLink) Start() error {
// If the config supplied watchtower client, ensure the channel is
// registered before trying to use it during operation.
if l.cfg.TowerClient != nil {
// TODO(halseth): support anchor types for watchtower.
state := l.channel.State()
if l.cfg.TowerClient != nil && state.ChanType.HasAnchors() {
l.log.Warnf("Skipping tower registration for anchor " +
"channel type")
} else if l.cfg.TowerClient != nil && !state.ChanType.HasAnchors() {
err := l.cfg.TowerClient.RegisterChannel(l.ChanID())
if err != nil {
return err
@ -1883,8 +1888,12 @@ func (l *channelLink) handleUpstreamMsg(msg lnwire.Message) {
// If we have a tower client, we'll proceed in backing up the
// state that was just revoked.
if l.cfg.TowerClient != nil {
state := l.channel.State()
// TODO(halseth): support anchor types for watchtower.
state := l.channel.State()
if l.cfg.TowerClient != nil && state.ChanType.HasAnchors() {
l.log.Warnf("Skipping tower backup for anchor " +
"channel type")
} else if l.cfg.TowerClient != nil && !state.ChanType.HasAnchors() {
breachInfo, err := lnwallet.NewBreachRetribution(
state, state.RemoteCommitment.CommitHeight-1, 0,
)

@ -156,15 +156,16 @@ type HtlcSucceedInput struct {
// MakeHtlcSucceedInput assembles a new redeem input that can be used to
// construct a sweep transaction.
func MakeHtlcSucceedInput(outpoint *wire.OutPoint,
signDescriptor *SignDescriptor,
preimage []byte, heightHint uint32) HtlcSucceedInput {
signDescriptor *SignDescriptor, preimage []byte, heightHint,
blocksToMaturity uint32) HtlcSucceedInput {
return HtlcSucceedInput{
inputKit: inputKit{
outpoint: *outpoint,
witnessType: HtlcAcceptedRemoteSuccess,
signDesc: *signDescriptor,
heightHint: heightHint,
outpoint: *outpoint,
witnessType: HtlcAcceptedRemoteSuccess,
signDesc: *signDescriptor,
heightHint: heightHint,
blockToMaturity: blocksToMaturity,
},
preimage: preimage,
}

@ -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> <sendr sig> <recvr sig> <0> (spend using HTLC timeout transaction)
// RECVR: <recvr sig> <preimage>
@ -168,9 +171,11 @@ func Ripemd160H(d []byte) []byte {
// OP_HASH160 <ripemd160(payment hash)> 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)
@ -330,8 +343,10 @@ func SenderHtlcSpendRedeem(signer Signer, signDesc *SignDescriptor,
// HTLC to activate the time locked covenant clause of a soon to be expired
// HTLC. This script simply spends the multi-sig output using the
// pre-generated HTLC timeout transaction.
func SenderHtlcSpendTimeout(receiverSig []byte, signer Signer,
signDesc *SignDescriptor, htlcTimeoutTx *wire.MsgTx) (wire.TxWitness, error) {
func SenderHtlcSpendTimeout(receiverSig []byte,
receiverSigHash txscript.SigHashType, signer Signer,
signDesc *SignDescriptor, htlcTimeoutTx *wire.MsgTx) (
wire.TxWitness, error) {
sweepSig, err := signer.SignOutputRaw(htlcTimeoutTx, signDesc)
if err != nil {
@ -344,7 +359,7 @@ func SenderHtlcSpendTimeout(receiverSig []byte, signer Signer,
// original OP_CHECKMULTISIG.
witnessStack := wire.TxWitness(make([][]byte, 5))
witnessStack[0] = nil
witnessStack[1] = append(receiverSig, byte(txscript.SigHashAll))
witnessStack[1] = append(receiverSig, byte(receiverSigHash))
witnessStack[2] = append(sweepSig, byte(signDesc.HashType))
witnessStack[3] = nil
witnessStack[4] = signDesc.WitnessScript
@ -362,6 +377,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> <sender sig> <recvr sig> <preimage> (spend using HTLC success transaction)
// REVOK: <sig> <key>
@ -381,10 +399,11 @@ func SenderHtlcSpendTimeout(receiverSig []byte, signer Signer,
// OP_DROP <cltv expiry> 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 +486,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)
@ -481,9 +508,10 @@ func ReceiverHTLCScript(cltvExpiry uint32, senderHtlcKey,
// signed has a relative timelock delay enforced by its sequence number. This
// delay give the sender of the HTLC enough time to revoke the output if this
// is a breach commitment transaction.
func ReceiverHtlcSpendRedeem(senderSig, paymentPreimage []byte,
signer Signer, signDesc *SignDescriptor,
htlcSuccessTx *wire.MsgTx) (wire.TxWitness, error) {
func ReceiverHtlcSpendRedeem(senderSig []byte,
senderSigHash txscript.SigHashType, paymentPreimage []byte,
signer Signer, signDesc *SignDescriptor, htlcSuccessTx *wire.MsgTx) (
wire.TxWitness, error) {
// First, we'll generate a signature for the HTLC success transaction.
// The signDesc should be signing with the public key used as the
@ -499,7 +527,7 @@ func ReceiverHtlcSpendRedeem(senderSig, paymentPreimage []byte,
// order to consume the extra pop within OP_CHECKMULTISIG.
witnessStack := wire.TxWitness(make([][]byte, 5))
witnessStack[0] = nil
witnessStack[1] = append(senderSig, byte(txscript.SigHashAll))
witnessStack[1] = append(senderSig, byte(senderSigHash))
witnessStack[2] = append(sweepSig, byte(signDesc.HashType))
witnessStack[3] = paymentPreimage
witnessStack[4] = signDesc.WitnessScript
@ -828,18 +856,6 @@ func CommitScriptToSelf(csvTimeout uint32, selfKey, revokeKey *btcec.PublicKey)
return builder.Script()
}
// CommitScriptUnencumbered constructs the public key script on the commitment
// transaction paying to the "other" party. The constructed output is a normal
// p2wkh output spendable immediately, requiring no contestation period.
func CommitScriptUnencumbered(key *btcec.PublicKey) ([]byte, error) {
// This script goes to the "other" party, and is spendable immediately.
builder := txscript.NewScriptBuilder()
builder.AddOp(txscript.OP_0)
builder.AddData(btcutil.Hash160(key.SerializeCompressed()))
return builder.Script()
}
// CommitSpendTimeout constructs a valid witness allowing the owner of a
// particular commitment transaction to spend the output returning settled
// funds back to themselves after a relative block timeout. In order to
@ -948,6 +964,137 @@ func CommitSpendNoDelay(signer Signer, signDesc *SignDescriptor,
return witness, nil
}
// CommitScriptUnencumbered constructs the public key script on the commitment
// transaction paying to the "other" party. The constructed output is a normal
// p2wkh output spendable immediately, requiring no contestation period.
func CommitScriptUnencumbered(key *btcec.PublicKey) ([]byte, error) {
// This script goes to the "other" party, and is spendable immediately.
builder := txscript.NewScriptBuilder()
builder.AddOp(txscript.OP_0)
builder.AddData(btcutil.Hash160(key.SerializeCompressed()))
return builder.Script()
}
// CommitScriptToRemoteConfirmed constructs the script for the output on the
// commitment transaction paying to the remote party of said commitment
// transaction. The money can only be spend after one confirmation.
//
// Possible Input Scripts:
// SWEEP: <sig>
//
// Output Script:
// <key> OP_CHECKSIGVERIFY
// 1 OP_CHECKSEQUENCEVERIFY
func CommitScriptToRemoteConfirmed(key *btcec.PublicKey) ([]byte, error) {
builder := txscript.NewScriptBuilder()
// Only the given key can spend the output.
builder.AddData(key.SerializeCompressed())
builder.AddOp(txscript.OP_CHECKSIGVERIFY)
// Check that the it has one confirmation.
builder.AddOp(txscript.OP_1)
builder.AddOp(txscript.OP_CHECKSEQUENCEVERIFY)
return builder.Script()
}
// CommitSpendToRemoteConfirmed constructs a valid witness allowing a node to
// spend their settled output on the counterparty's commitment transaction when
// it has one confirmetion. This is used for the anchor channel type. The
// spending key will always be non-tweaked for this output type.
func CommitSpendToRemoteConfirmed(signer Signer, signDesc *SignDescriptor,
sweepTx *wire.MsgTx) (wire.TxWitness, error) {
if signDesc.KeyDesc.PubKey == nil {
return nil, fmt.Errorf("cannot generate witness with nil " +
"KeyDesc pubkey")
}
// Similar to non delayed output, only a signature is needed.
sweepSig, err := signer.SignOutputRaw(sweepTx, signDesc)
if err != nil {
return nil, err
}
// Finally, we'll manually craft the witness. The witness here is the
// signature and the redeem script.
witnessStack := make([][]byte, 2)
witnessStack[0] = append(sweepSig, byte(signDesc.HashType))
witnessStack[1] = signDesc.WitnessScript
return witnessStack, nil
}
// CommitScriptAnchor constructs the script for the anchor output spendable by
// the given key immediately, or by anyone after 16 confirmations.
//
// Possible Input Scripts:
// By owner: <sig>
// By anyone (after 16 conf): <emptyvector>
//
// Output Script:
// <funding_pubkey> OP_CHECKSIG OP_IFDUP
// OP_NOTIF
// OP_16 OP_CSV
// OP_ENDIF
func CommitScriptAnchor(key *btcec.PublicKey) ([]byte, error) {
builder := txscript.NewScriptBuilder()
// Spend immediately with key.
builder.AddData(key.SerializeCompressed())
builder.AddOp(txscript.OP_CHECKSIG)
// Duplicate the value if true, since it will be consumed by the NOTIF.
builder.AddOp(txscript.OP_IFDUP)
// Otherwise spendable by anyone after 16 confirmations.
builder.AddOp(txscript.OP_NOTIF)
builder.AddOp(txscript.OP_16)
builder.AddOp(txscript.OP_CHECKSEQUENCEVERIFY)
builder.AddOp(txscript.OP_ENDIF)
return builder.Script()
}
// CommitSpendAnchor constructs a valid witness allowing a node to spend their
// anchor output on the commitment transaction using their funding key. This is
// used for the anchor channel type.
func CommitSpendAnchor(signer Signer, signDesc *SignDescriptor,
sweepTx *wire.MsgTx) (wire.TxWitness, error) {
if signDesc.KeyDesc.PubKey == nil {
return nil, fmt.Errorf("cannot generate witness with nil " +
"KeyDesc pubkey")
}
// Create a signature.
sweepSig, err := signer.SignOutputRaw(sweepTx, signDesc)
if err != nil {
return nil, err
}
// The witness here is just a signature and the redeem script.
witnessStack := make([][]byte, 2)
witnessStack[0] = append(sweepSig, byte(signDesc.HashType))
witnessStack[1] = signDesc.WitnessScript
return witnessStack, nil
}
// CommitSpendAnchorAnyone constructs a witness allowing anyone to spend the
// anchor output after it has gotten 16 confirmations. Since no signing is
// required, only knowledge of the redeem script is necessary to spend it.
func CommitSpendAnchorAnyone(script []byte) (wire.TxWitness, error) {
// The witness here is just the redeem script.
witnessStack := make([][]byte, 2)
witnessStack[0] = nil
witnessStack[1] = script
return witnessStack, nil
}
// SingleTweakBytes computes set of bytes we call the single tweak. The purpose
// of the single tweak is to randomize all regular delay and payment base
// points. To do this, we generate a hash that binds the commitment point to

@ -15,6 +15,73 @@ import (
"github.com/lightningnetwork/lnd/keychain"
)
// assertEngineExecution executes the VM returned by the newEngine closure,
// asserting the result matches the validity expectation. In the case where it
// doesn't match the expectation, it executes the script step-by-step and
// prints debug information to stdout.
func assertEngineExecution(t *testing.T, testNum int, valid bool,
newEngine func() (*txscript.Engine, error)) {
t.Helper()
// Get a new VM to execute.
vm, err := newEngine()
if err != nil {
t.Fatalf("unable to create engine: %v", err)
}
// Execute the VM, only go on to the step-by-step execution if
// it doesn't validate as expected.
vmErr := vm.Execute()
if valid == (vmErr == nil) {
return
}
// Now that the execution didn't match what we expected, fetch a new VM
// to step through.
vm, err = newEngine()
if err != nil {
t.Fatalf("unable to create engine: %v", err)
}
// This buffer will trace execution of the Script, dumping out
// to stdout.
var debugBuf bytes.Buffer
done := false
for !done {
dis, err := vm.DisasmPC()
if err != nil {
t.Fatalf("stepping (%v)\n", err)
}
debugBuf.WriteString(fmt.Sprintf("stepping %v\n", dis))
done, err = vm.Step()
if err != nil && valid {
fmt.Println(debugBuf.String())
t.Fatalf("spend test case #%v failed, spend "+
"should be valid: %v", testNum, err)
} else if err == nil && !valid && done {
fmt.Println(debugBuf.String())
t.Fatalf("spend test case #%v succeed, spend "+
"should be invalid: %v", testNum, err)
}
debugBuf.WriteString(fmt.Sprintf("Stack: %v", vm.GetStack()))
debugBuf.WriteString(fmt.Sprintf("AltStack: %v", vm.GetAltStack()))
}
// If we get to this point the unexpected case was not reached
// during step execution, which happens for some checks, like
// the clean-stack rule.
validity := "invalid"
if valid {
validity = "valid"
}
fmt.Println(debugBuf.String())
t.Fatalf("%v spend test case #%v execution ended with: %v", validity, testNum, vmErr)
}
// TestRevocationKeyDerivation tests that given a public key, and a revocation
// hash, the homomorphic revocation public and private key derivation work
// properly.
@ -145,43 +212,6 @@ func TestHTLCSenderSpendValidation(t *testing.T) {
// we'll be using Bob's base point for the revocation key.
revocationKey := DeriveRevocationPubkey(bobKeyPub, commitPoint)
// Generate the raw HTLC redemption scripts, and its p2wsh counterpart.
htlcWitnessScript, err := SenderHTLCScript(aliceLocalKey, bobLocalKey,
revocationKey, paymentHash[:])
if err != nil {
t.Fatalf("unable to create htlc sender script: %v", err)
}
htlcPkScript, err := WitnessScriptHash(htlcWitnessScript)
if err != nil {
t.Fatalf("unable to create p2wsh htlc script: %v", err)
}
// This will be Alice's commitment transaction. In this scenario Alice
// is sending an HTLC to a node she has a path to (could be Bob, could
// be multiple hops down, it doesn't really matter).
htlcOutput := &wire.TxOut{
Value: int64(paymentAmt),
PkScript: htlcPkScript,
}
senderCommitTx := wire.NewMsgTx(2)
senderCommitTx.AddTxIn(fakeFundingTxIn)
senderCommitTx.AddTxOut(htlcOutput)
prevOut := &wire.OutPoint{
Hash: senderCommitTx.TxHash(),
Index: 0,
}
sweepTx := wire.NewMsgTx(2)
sweepTx.AddTxIn(wire.NewTxIn(prevOut, nil, nil))
sweepTx.AddTxOut(
&wire.TxOut{
PkScript: []byte("doesn't matter"),
Value: 1 * 10e8,
},
)
sweepTxSigHashes := txscript.NewTxSigHashes(sweepTx)
bobCommitTweak := SingleTweakBytes(commitPoint, bobKeyPub)
aliceCommitTweak := SingleTweakBytes(commitPoint, aliceKeyPub)
@ -191,23 +221,92 @@ func TestHTLCSenderSpendValidation(t *testing.T) {
bobSigner := &MockSigner{Privkeys: []*btcec.PrivateKey{bobKeyPriv}}
aliceSigner := &MockSigner{Privkeys: []*btcec.PrivateKey{aliceKeyPriv}}
// We'll also generate a signature on the sweep transaction above
// that will act as Bob's signature to Alice for the second level HTLC
// transaction.
bobSignDesc := SignDescriptor{
KeyDesc: keychain.KeyDescriptor{
PubKey: bobKeyPub,
},
SingleTweak: bobCommitTweak,
WitnessScript: htlcWitnessScript,
Output: htlcOutput,
HashType: txscript.SigHashAll,
SigHashes: sweepTxSigHashes,
InputIndex: 0,
var (
htlcWitnessScript, htlcPkScript []byte
htlcOutput *wire.TxOut
sweepTxSigHashes *txscript.TxSigHashes
senderCommitTx, sweepTx *wire.MsgTx
bobRecvrSig []byte
bobSigHash txscript.SigHashType
)
// genCommitTx generates a commitment tx where the htlc output requires
// confirmation to be spent according to 'confirmed'.
genCommitTx := func(confirmed bool) {
// Generate the raw HTLC redemption scripts, and its p2wsh
// counterpart.
htlcWitnessScript, err = SenderHTLCScript(
aliceLocalKey, bobLocalKey, revocationKey,
paymentHash[:], confirmed,
)
if err != nil {
t.Fatalf("unable to create htlc sender script: %v", err)
}
htlcPkScript, err = WitnessScriptHash(htlcWitnessScript)
if err != nil {
t.Fatalf("unable to create p2wsh htlc script: %v", err)
}
// This will be Alice's commitment transaction. In this
// scenario Alice is sending an HTLC to a node she has a path
// to (could be Bob, could be multiple hops down, it doesn't
// really matter).
htlcOutput = &wire.TxOut{
Value: int64(paymentAmt),
PkScript: htlcPkScript,
}
senderCommitTx = wire.NewMsgTx(2)
senderCommitTx.AddTxIn(fakeFundingTxIn)
senderCommitTx.AddTxOut(htlcOutput)
}
bobRecvrSig, err := bobSigner.SignOutputRaw(sweepTx, &bobSignDesc)
if err != nil {
t.Fatalf("unable to generate alice signature: %v", err)
// genSweepTx generates a sweep of the senderCommitTx, and sets the
// sequence and sighash single|anyonecanspend if confirmed is true.
genSweepTx := func(confirmed bool) {
prevOut := &wire.OutPoint{
Hash: senderCommitTx.TxHash(),
Index: 0,
}
sweepTx = wire.NewMsgTx(2)
sweepTx.AddTxIn(wire.NewTxIn(prevOut, nil, nil))
if confirmed {
sweepTx.TxIn[0].Sequence = LockTimeToSequence(false, 1)
}
sweepTx.AddTxOut(
&wire.TxOut{
PkScript: []byte("doesn't matter"),
Value: 1 * 10e8,
},
)
sweepTxSigHashes = txscript.NewTxSigHashes(sweepTx)
bobSigHash = txscript.SigHashAll
if confirmed {
bobSigHash = txscript.SigHashSingle | txscript.SigHashAnyOneCanPay
}
// We'll also generate a signature on the sweep transaction above
// that will act as Bob's signature to Alice for the second level HTLC
// transaction.
bobSignDesc := SignDescriptor{
KeyDesc: keychain.KeyDescriptor{
PubKey: bobKeyPub,
},
SingleTweak: bobCommitTweak,
WitnessScript: htlcWitnessScript,
Output: htlcOutput,
HashType: bobSigHash,
SigHashes: sweepTxSigHashes,
InputIndex: 0,
}
bobRecvrSig, err = bobSigner.SignOutputRaw(sweepTx, &bobSignDesc)
if err != nil {
t.Fatalf("unable to generate alice signature: %v", err)
}
}
testCases := []struct {
@ -218,6 +317,9 @@ func TestHTLCSenderSpendValidation(t *testing.T) {
// revoke w/ sig
// TODO(roasbeef): test invalid revoke
makeWitnessTestCase(t, func() (wire.TxWitness, error) {
genCommitTx(false)
genSweepTx(false)
signDesc := &SignDescriptor{
KeyDesc: keychain.KeyDescriptor{
PubKey: bobKeyPub,
@ -238,6 +340,9 @@ func TestHTLCSenderSpendValidation(t *testing.T) {
{
// HTLC with invalid preimage size
makeWitnessTestCase(t, func() (wire.TxWitness, error) {
genCommitTx(false)
genSweepTx(false)
signDesc := &SignDescriptor{
KeyDesc: keychain.KeyDescriptor{
PubKey: bobKeyPub,
@ -261,6 +366,9 @@ func TestHTLCSenderSpendValidation(t *testing.T) {
// HTLC with valid preimage size + sig
// TODO(roasbeef): invalid preimage
makeWitnessTestCase(t, func() (wire.TxWitness, error) {
genCommitTx(false)
genSweepTx(false)
signDesc := &SignDescriptor{
KeyDesc: keychain.KeyDescriptor{
PubKey: bobKeyPub,
@ -278,11 +386,72 @@ func TestHTLCSenderSpendValidation(t *testing.T) {
}),
true,
},
{
// HTLC with valid preimage size + sig, and with
// enforced locktime in HTLC script.
makeWitnessTestCase(t, func() (wire.TxWitness, error) {
// Make a commit tx that needs confirmation for
// HTLC output to be spent.
genCommitTx(true)
// Generate a sweep with the locktime set.
genSweepTx(true)
signDesc := &SignDescriptor{
KeyDesc: keychain.KeyDescriptor{
PubKey: bobKeyPub,
},
SingleTweak: bobCommitTweak,
WitnessScript: htlcWitnessScript,
Output: htlcOutput,
HashType: txscript.SigHashAll,
SigHashes: sweepTxSigHashes,
InputIndex: 0,
}
return SenderHtlcSpendRedeem(bobSigner, signDesc,
sweepTx, paymentPreimage)
}),
true,
},
{
// HTLC with valid preimage size + sig, but trying to
// spend CSV output without sequence set.
makeWitnessTestCase(t, func() (wire.TxWitness, error) {
// Generate commitment tx with 1 CSV locked
// HTLC.
genCommitTx(true)
// Generate sweep tx that doesn't have locktime
// enabled.
genSweepTx(false)
signDesc := &SignDescriptor{
KeyDesc: keychain.KeyDescriptor{
PubKey: bobKeyPub,
},
SingleTweak: bobCommitTweak,
WitnessScript: htlcWitnessScript,
Output: htlcOutput,
HashType: txscript.SigHashAll,
SigHashes: sweepTxSigHashes,
InputIndex: 0,
}
return SenderHtlcSpendRedeem(bobSigner, signDesc,
sweepTx, paymentPreimage)
}),
false,
},
{
// valid spend to the transition the state of the HTLC
// output with the second level HTLC timeout
// transaction.
makeWitnessTestCase(t, func() (wire.TxWitness, error) {
genCommitTx(false)
genSweepTx(false)
signDesc := &SignDescriptor{
KeyDesc: keychain.KeyDescriptor{
PubKey: aliceKeyPub,
@ -295,11 +464,76 @@ func TestHTLCSenderSpendValidation(t *testing.T) {
InputIndex: 0,
}
return SenderHtlcSpendTimeout(bobRecvrSig, aliceSigner,
signDesc, sweepTx)
return SenderHtlcSpendTimeout(
bobRecvrSig, bobSigHash, aliceSigner,
signDesc, sweepTx,
)
}),
true,
},
{
// valid spend to the transition the state of the HTLC
// output with the second level HTLC timeout
// transaction.
makeWitnessTestCase(t, func() (wire.TxWitness, error) {
// Make a commit tx that needs confirmation for
// HTLC output to be spent.
genCommitTx(true)
// Generate a sweep with the locktime set.
genSweepTx(true)
signDesc := &SignDescriptor{
KeyDesc: keychain.KeyDescriptor{
PubKey: aliceKeyPub,
},
SingleTweak: aliceCommitTweak,
WitnessScript: htlcWitnessScript,
Output: htlcOutput,
HashType: txscript.SigHashAll,
SigHashes: sweepTxSigHashes,
InputIndex: 0,
}
return SenderHtlcSpendTimeout(
bobRecvrSig, bobSigHash, aliceSigner,
signDesc, sweepTx,
)
}),
true,
},
{
// valid spend to the transition the state of the HTLC
// output with the second level HTLC timeout
// transaction.
makeWitnessTestCase(t, func() (wire.TxWitness, error) {
// Generate commitment tx with 1 CSV locked
// HTLC.
genCommitTx(true)
// Generate sweep tx that doesn't have locktime
// enabled.
genSweepTx(false)
signDesc := &SignDescriptor{
KeyDesc: keychain.KeyDescriptor{
PubKey: aliceKeyPub,
},
SingleTweak: aliceCommitTweak,
WitnessScript: htlcWitnessScript,
Output: htlcOutput,
HashType: txscript.SigHashAll,
SigHashes: sweepTxSigHashes,
InputIndex: 0,
}
return SenderHtlcSpendTimeout(
bobRecvrSig, bobSigHash, aliceSigner,
signDesc, sweepTx,
)
}),
false,
},
}
// TODO(roasbeef): set of cases to ensure able to sign w/ keypath and
@ -308,39 +542,13 @@ func TestHTLCSenderSpendValidation(t *testing.T) {
for i, testCase := range testCases {
sweepTx.TxIn[0].Witness = testCase.witness()
vm, err := txscript.NewEngine(htlcPkScript,
sweepTx, 0, txscript.StandardVerifyFlags, nil,
nil, int64(paymentAmt))
if err != nil {
t.Fatalf("unable to create engine: %v", err)
newEngine := func() (*txscript.Engine, error) {
return txscript.NewEngine(htlcPkScript,
sweepTx, 0, txscript.StandardVerifyFlags, nil,
nil, int64(paymentAmt))
}
// This buffer will trace execution of the Script, only dumping
// out to stdout in the case that a test fails.
var debugBuf bytes.Buffer
done := false
for !done {
dis, err := vm.DisasmPC()
if err != nil {
t.Fatalf("stepping (%v)\n", err)
}
debugBuf.WriteString(fmt.Sprintf("stepping %v\n", dis))
done, err = vm.Step()
if err != nil && testCase.valid {
fmt.Println(debugBuf.String())
t.Fatalf("spend test case #%v failed, spend "+
"should be valid: %v", i, err)
} else if err == nil && !testCase.valid && done {
fmt.Println(debugBuf.String())
t.Fatalf("spend test case #%v succeed, spend "+
"should be invalid: %v", i, err)
}
debugBuf.WriteString(fmt.Sprintf("Stack: %v", vm.GetStack()))
debugBuf.WriteString(fmt.Sprintf("AltStack: %v", vm.GetAltStack()))
}
assertEngineExecution(t, i, testCase.valid, newEngine)
}
}
@ -400,46 +608,6 @@ func TestHTLCReceiverSpendValidation(t *testing.T) {
// be using Alice's base point for the revocation key.
revocationKey := DeriveRevocationPubkey(aliceKeyPub, commitPoint)
// Generate the raw HTLC redemption scripts, and its p2wsh counterpart.
htlcWitnessScript, err := ReceiverHTLCScript(cltvTimeout, aliceLocalKey,
bobLocalKey, revocationKey, paymentHash[:])
if err != nil {
t.Fatalf("unable to create htlc sender script: %v", err)
}
htlcPkScript, err := WitnessScriptHash(htlcWitnessScript)
if err != nil {
t.Fatalf("unable to create p2wsh htlc script: %v", err)
}
// This will be Bob's commitment transaction. In this scenario Alice is
// sending an HTLC to a node she has a path to (could be Bob, could be
// multiple hops down, it doesn't really matter).
htlcOutput := &wire.TxOut{
Value: int64(paymentAmt),
PkScript: htlcWitnessScript,
}
receiverCommitTx := wire.NewMsgTx(2)
receiverCommitTx.AddTxIn(fakeFundingTxIn)
receiverCommitTx.AddTxOut(htlcOutput)
prevOut := &wire.OutPoint{
Hash: receiverCommitTx.TxHash(),
Index: 0,
}
sweepTx := wire.NewMsgTx(2)
sweepTx.AddTxIn(&wire.TxIn{
PreviousOutPoint: *prevOut,
})
sweepTx.AddTxOut(
&wire.TxOut{
PkScript: []byte("doesn't matter"),
Value: 1 * 10e8,
},
)
sweepTxSigHashes := txscript.NewTxSigHashes(sweepTx)
bobCommitTweak := SingleTweakBytes(commitPoint, bobKeyPub)
aliceCommitTweak := SingleTweakBytes(commitPoint, aliceKeyPub)
@ -449,23 +617,88 @@ func TestHTLCReceiverSpendValidation(t *testing.T) {
bobSigner := &MockSigner{Privkeys: []*btcec.PrivateKey{bobKeyPriv}}
aliceSigner := &MockSigner{Privkeys: []*btcec.PrivateKey{aliceKeyPriv}}
// We'll also generate a signature on the sweep transaction above
// that will act as Alice's signature to Bob for the second level HTLC
// transaction.
aliceSignDesc := SignDescriptor{
KeyDesc: keychain.KeyDescriptor{
PubKey: aliceKeyPub,
},
SingleTweak: aliceCommitTweak,
WitnessScript: htlcWitnessScript,
Output: htlcOutput,
HashType: txscript.SigHashAll,
SigHashes: sweepTxSigHashes,
InputIndex: 0,
var (
htlcWitnessScript, htlcPkScript []byte
htlcOutput *wire.TxOut
receiverCommitTx, sweepTx *wire.MsgTx
sweepTxSigHashes *txscript.TxSigHashes
aliceSenderSig []byte
aliceSigHash txscript.SigHashType
)
genCommitTx := func(confirmed bool) {
// Generate the raw HTLC redemption scripts, and its p2wsh
// counterpart.
htlcWitnessScript, err = ReceiverHTLCScript(
cltvTimeout, aliceLocalKey, bobLocalKey, revocationKey,
paymentHash[:], confirmed,
)
if err != nil {
t.Fatalf("unable to create htlc sender script: %v", err)
}
htlcPkScript, err = WitnessScriptHash(htlcWitnessScript)
if err != nil {
t.Fatalf("unable to create p2wsh htlc script: %v", err)
}
// This will be Bob's commitment transaction. In this scenario Alice is
// sending an HTLC to a node she has a path to (could be Bob, could be
// multiple hops down, it doesn't really matter).
htlcOutput = &wire.TxOut{
Value: int64(paymentAmt),
PkScript: htlcWitnessScript,
}
receiverCommitTx = wire.NewMsgTx(2)
receiverCommitTx.AddTxIn(fakeFundingTxIn)
receiverCommitTx.AddTxOut(htlcOutput)
}
aliceSenderSig, err := aliceSigner.SignOutputRaw(sweepTx, &aliceSignDesc)
if err != nil {
t.Fatalf("unable to generate alice signature: %v", err)
genSweepTx := func(confirmed bool) {
prevOut := &wire.OutPoint{
Hash: receiverCommitTx.TxHash(),
Index: 0,
}
sweepTx = wire.NewMsgTx(2)
sweepTx.AddTxIn(&wire.TxIn{
PreviousOutPoint: *prevOut,
})
if confirmed {
sweepTx.TxIn[0].Sequence = LockTimeToSequence(false, 1)
}
sweepTx.AddTxOut(
&wire.TxOut{
PkScript: []byte("doesn't matter"),
Value: 1 * 10e8,
},
)
sweepTxSigHashes = txscript.NewTxSigHashes(sweepTx)
aliceSigHash = txscript.SigHashAll
if confirmed {
aliceSigHash = txscript.SigHashSingle | txscript.SigHashAnyOneCanPay
}
// We'll also generate a signature on the sweep transaction above
// that will act as Alice's signature to Bob for the second level HTLC
// transaction.
aliceSignDesc := SignDescriptor{
KeyDesc: keychain.KeyDescriptor{
PubKey: aliceKeyPub,
},
SingleTweak: aliceCommitTweak,
WitnessScript: htlcWitnessScript,
Output: htlcOutput,
HashType: aliceSigHash,
SigHashes: sweepTxSigHashes,
InputIndex: 0,
}
aliceSenderSig, err = aliceSigner.SignOutputRaw(sweepTx, &aliceSignDesc)
if err != nil {
t.Fatalf("unable to generate alice signature: %v", err)
}
}
// TODO(roasbeef): modify valid to check precise script errors?
@ -476,6 +709,9 @@ func TestHTLCReceiverSpendValidation(t *testing.T) {
{
// HTLC redemption w/ invalid preimage size
makeWitnessTestCase(t, func() (wire.TxWitness, error) {
genCommitTx(false)
genSweepTx(false)
signDesc := &SignDescriptor{
KeyDesc: keychain.KeyDescriptor{
PubKey: bobKeyPub,
@ -488,9 +724,11 @@ func TestHTLCReceiverSpendValidation(t *testing.T) {
InputIndex: 0,
}
return ReceiverHtlcSpendRedeem(aliceSenderSig,
return ReceiverHtlcSpendRedeem(
aliceSenderSig, aliceSigHash,
bytes.Repeat([]byte{1}, 45), bobSigner,
signDesc, sweepTx)
signDesc, sweepTx,
)
}),
false,
@ -498,6 +736,9 @@ func TestHTLCReceiverSpendValidation(t *testing.T) {
{
// HTLC redemption w/ valid preimage size
makeWitnessTestCase(t, func() (wire.TxWitness, error) {
genCommitTx(false)
genSweepTx(false)
signDesc := &SignDescriptor{
KeyDesc: keychain.KeyDescriptor{
PubKey: bobKeyPub,
@ -510,15 +751,20 @@ func TestHTLCReceiverSpendValidation(t *testing.T) {
InputIndex: 0,
}
return ReceiverHtlcSpendRedeem(aliceSenderSig,
paymentPreimage[:], bobSigner,
signDesc, sweepTx)
return ReceiverHtlcSpendRedeem(
aliceSenderSig, aliceSigHash,
paymentPreimage, bobSigner,
signDesc, sweepTx,
)
}),
true,
},
{
// revoke w/ sig
makeWitnessTestCase(t, func() (wire.TxWitness, error) {
genCommitTx(false)
genSweepTx(false)
signDesc := &SignDescriptor{
KeyDesc: keychain.KeyDescriptor{
PubKey: aliceKeyPub,
@ -536,9 +782,76 @@ func TestHTLCReceiverSpendValidation(t *testing.T) {
}),
true,
},
{
// HTLC redemption w/ valid preimage size, and with
// enforced locktime in HTLC scripts.
makeWitnessTestCase(t, func() (wire.TxWitness, error) {
// Make a commit tx that needs confirmation for
// HTLC output to be spent.
genCommitTx(true)
// Generate a sweep with the locktime set.
genSweepTx(true)
signDesc := &SignDescriptor{
KeyDesc: keychain.KeyDescriptor{
PubKey: bobKeyPub,
},
SingleTweak: bobCommitTweak,
WitnessScript: htlcWitnessScript,
Output: htlcOutput,
HashType: txscript.SigHashAll,
SigHashes: sweepTxSigHashes,
InputIndex: 0,
}
return ReceiverHtlcSpendRedeem(
aliceSenderSig, aliceSigHash,
paymentPreimage, bobSigner,
signDesc, sweepTx,
)
}),
true,
},
{
// HTLC redemption w/ valid preimage size, but trying
// to spend CSV output without sequence set.
makeWitnessTestCase(t, func() (wire.TxWitness, error) {
// Generate commitment tx with 1 CSV locked
// HTLC.
genCommitTx(true)
// Generate sweep tx that doesn't have locktime
// enabled.
genSweepTx(false)
signDesc := &SignDescriptor{
KeyDesc: keychain.KeyDescriptor{
PubKey: bobKeyPub,
},
SingleTweak: bobCommitTweak,
WitnessScript: htlcWitnessScript,
Output: htlcOutput,
HashType: txscript.SigHashAll,
SigHashes: sweepTxSigHashes,
InputIndex: 0,
}
return ReceiverHtlcSpendRedeem(
aliceSenderSig, aliceSigHash,
paymentPreimage, bobSigner, signDesc,
sweepTx,
)
}),
false,
},
{
// refund w/ invalid lock time
makeWitnessTestCase(t, func() (wire.TxWitness, error) {
genCommitTx(false)
genSweepTx(false)
signDesc := &SignDescriptor{
KeyDesc: keychain.KeyDescriptor{
PubKey: aliceKeyPub,
@ -559,6 +872,9 @@ func TestHTLCReceiverSpendValidation(t *testing.T) {
{
// refund w/ valid lock time
makeWitnessTestCase(t, func() (wire.TxWitness, error) {
genCommitTx(false)
genSweepTx(false)
signDesc := &SignDescriptor{
KeyDesc: keychain.KeyDescriptor{
PubKey: aliceKeyPub,
@ -576,42 +892,75 @@ func TestHTLCReceiverSpendValidation(t *testing.T) {
}),
true,
},
{
// refund w/ valid lock time, and enforced locktime in
// HTLC scripts.
makeWitnessTestCase(t, func() (wire.TxWitness, error) {
// Make a commit tx that needs confirmation for
// HTLC output to be spent.
genCommitTx(true)
// Generate a sweep with the locktime set.
genSweepTx(true)
signDesc := &SignDescriptor{
KeyDesc: keychain.KeyDescriptor{
PubKey: aliceKeyPub,
},
SingleTweak: aliceCommitTweak,
WitnessScript: htlcWitnessScript,
Output: htlcOutput,
HashType: txscript.SigHashAll,
SigHashes: sweepTxSigHashes,
InputIndex: 0,
}
return ReceiverHtlcSpendTimeout(aliceSigner, signDesc,
sweepTx, int32(cltvTimeout))
}),
true,
},
{
// refund w/ valid lock time, but no sequence set in
// sweep tx trying to spend CSV locked HTLC output.
makeWitnessTestCase(t, func() (wire.TxWitness, error) {
// Generate commitment tx with 1 CSV locked
// HTLC.
genCommitTx(true)
// Generate sweep tx that doesn't have locktime
// enabled.
genSweepTx(false)
signDesc := &SignDescriptor{
KeyDesc: keychain.KeyDescriptor{
PubKey: aliceKeyPub,
},
SingleTweak: aliceCommitTweak,
WitnessScript: htlcWitnessScript,
Output: htlcOutput,
HashType: txscript.SigHashAll,
SigHashes: sweepTxSigHashes,
InputIndex: 0,
}
return ReceiverHtlcSpendTimeout(aliceSigner, signDesc,
sweepTx, int32(cltvTimeout))
}),
false,
},
}
for i, testCase := range testCases {
sweepTx.TxIn[0].Witness = testCase.witness()
vm, err := txscript.NewEngine(htlcPkScript,
sweepTx, 0, txscript.StandardVerifyFlags, nil,
nil, int64(paymentAmt))
if err != nil {
t.Fatalf("unable to create engine: %v", err)
newEngine := func() (*txscript.Engine, error) {
return txscript.NewEngine(htlcPkScript,
sweepTx, 0, txscript.StandardVerifyFlags, nil,
nil, int64(paymentAmt))
}
// This buffer will trace execution of the Script, only dumping
// out to stdout in the case that a test fails.
var debugBuf bytes.Buffer
done := false
for !done {
dis, err := vm.DisasmPC()
if err != nil {
t.Fatalf("stepping (%v)\n", err)
}
debugBuf.WriteString(fmt.Sprintf("stepping %v\n", dis))
done, err = vm.Step()
if err != nil && testCase.valid {
fmt.Println(debugBuf.String())
t.Fatalf("spend test case #%v failed, spend should be valid: %v", i, err)
} else if err == nil && !testCase.valid && done {
fmt.Println(debugBuf.String())
t.Fatalf("spend test case #%v succeed, spend should be invalid: %v", i, err)
}
debugBuf.WriteString(fmt.Sprintf("Stack: %v", vm.GetStack()))
debugBuf.WriteString(fmt.Sprintf("AltStack: %v", vm.GetAltStack()))
}
assertEngineExecution(t, i, testCase.valid, newEngine)
}
}
@ -811,39 +1160,227 @@ func TestSecondLevelHtlcSpends(t *testing.T) {
for i, testCase := range testCases {
sweepTx.TxIn[0].Witness = testCase.witness()
vm, err := txscript.NewEngine(htlcPkScript,
sweepTx, 0, txscript.StandardVerifyFlags, nil,
nil, int64(htlcAmt))
if err != nil {
t.Fatalf("unable to create engine: %v", err)
newEngine := func() (*txscript.Engine, error) {
return txscript.NewEngine(htlcPkScript,
sweepTx, 0, txscript.StandardVerifyFlags, nil,
nil, int64(htlcAmt))
}
// This buffer will trace execution of the Script, only dumping
// out to stdout in the case that a test fails.
var debugBuf bytes.Buffer
assertEngineExecution(t, i, testCase.valid, newEngine)
}
}
done := false
for !done {
dis, err := vm.DisasmPC()
if err != nil {
t.Fatalf("stepping (%v)\n", err)
}
debugBuf.WriteString(fmt.Sprintf("stepping %v\n", dis))
// TestCommitSpendToRemoteConfirmed checks that the delayed version of the
// to_remote version can only be spent by the owner, and after one
// confirmation.
func TestCommitSpendToRemoteConfirmed(t *testing.T) {
t.Parallel()
done, err = vm.Step()
if err != nil && testCase.valid {
fmt.Println(debugBuf.String())
t.Fatalf("spend test case #%v failed, spend "+
"should be valid: %v", i, err)
} else if err == nil && !testCase.valid && done {
fmt.Println(debugBuf.String())
t.Fatalf("spend test case #%v succeed, spend "+
"should be invalid: %v", i, err)
}
const outputVal = btcutil.Amount(2 * 10e8)
debugBuf.WriteString(fmt.Sprintf("Stack: %v", vm.GetStack()))
debugBuf.WriteString(fmt.Sprintf("AltStack: %v", vm.GetAltStack()))
aliceKeyPriv, aliceKeyPub := btcec.PrivKeyFromBytes(btcec.S256(),
testWalletPrivKey)
txid, err := chainhash.NewHash(testHdSeed.CloneBytes())
if err != nil {
t.Fatalf("unable to create txid: %v", err)
}
commitOut := &wire.OutPoint{
Hash: *txid,
Index: 0,
}
commitScript, err := CommitScriptToRemoteConfirmed(aliceKeyPub)
if err != nil {
t.Fatalf("unable to create htlc script: %v", err)
}
commitPkScript, err := WitnessScriptHash(commitScript)
if err != nil {
t.Fatalf("unable to create htlc output: %v", err)
}
commitOutput := &wire.TxOut{
PkScript: commitPkScript,
Value: int64(outputVal),
}
sweepTx := wire.NewMsgTx(2)
sweepTx.AddTxIn(wire.NewTxIn(commitOut, nil, nil))
sweepTx.AddTxOut(
&wire.TxOut{
PkScript: []byte("doesn't matter"),
Value: 1 * 10e8,
},
)
aliceSigner := &MockSigner{Privkeys: []*btcec.PrivateKey{aliceKeyPriv}}
testCases := []struct {
witness func() wire.TxWitness
valid bool
}{
{
// Alice can spend after the a CSV delay has passed.
makeWitnessTestCase(t, func() (wire.TxWitness, error) {
sweepTx.TxIn[0].Sequence = LockTimeToSequence(false, 1)
sweepTxSigHashes := txscript.NewTxSigHashes(sweepTx)
signDesc := &SignDescriptor{
KeyDesc: keychain.KeyDescriptor{
PubKey: aliceKeyPub,
},
WitnessScript: commitScript,
Output: commitOutput,
HashType: txscript.SigHashAll,
SigHashes: sweepTxSigHashes,
InputIndex: 0,
}
return CommitSpendToRemoteConfirmed(aliceSigner, signDesc,
sweepTx)
}),
true,
},
{
// Alice cannot spend output without sequence set.
makeWitnessTestCase(t, func() (wire.TxWitness, error) {
sweepTx.TxIn[0].Sequence = wire.MaxTxInSequenceNum
sweepTxSigHashes := txscript.NewTxSigHashes(sweepTx)
signDesc := &SignDescriptor{
KeyDesc: keychain.KeyDescriptor{
PubKey: aliceKeyPub,
},
WitnessScript: commitScript,
Output: commitOutput,
HashType: txscript.SigHashAll,
SigHashes: sweepTxSigHashes,
InputIndex: 0,
}
return CommitSpendToRemoteConfirmed(aliceSigner, signDesc,
sweepTx)
}),
false,
},
}
for i, testCase := range testCases {
sweepTx.TxIn[0].Witness = testCase.witness()
newEngine := func() (*txscript.Engine, error) {
return txscript.NewEngine(commitPkScript,
sweepTx, 0, txscript.StandardVerifyFlags, nil,
nil, int64(outputVal))
}
assertEngineExecution(t, i, testCase.valid, newEngine)
}
}
// TestSpendAnchor checks that we can spend the anchors using the various spend
// paths.
func TestSpendAnchor(t *testing.T) {
t.Parallel()
const anchorSize = 294
// First we'll set up some initial key state for Alice.
aliceKeyPriv, aliceKeyPub := btcec.PrivKeyFromBytes(btcec.S256(),
testWalletPrivKey)
// Create a fake anchor outpoint that we'll use to generate the
// sweeping transaction.
txid, err := chainhash.NewHash(testHdSeed.CloneBytes())
if err != nil {
t.Fatalf("unable to create txid: %v", err)
}
anchorOutPoint := &wire.OutPoint{
Hash: *txid,
Index: 0,
}
sweepTx := wire.NewMsgTx(2)
sweepTx.AddTxIn(wire.NewTxIn(anchorOutPoint, nil, nil))
sweepTx.AddTxOut(
&wire.TxOut{
PkScript: []byte("doesn't matter"),
Value: 1 * 10e8,
},
)
// Generate the anchor script that can be spent by Alice immediately,
// or by anyone after 16 blocks.
anchorScript, err := CommitScriptAnchor(aliceKeyPub)
if err != nil {
t.Fatalf("unable to create htlc script: %v", err)
}
anchorPkScript, err := WitnessScriptHash(anchorScript)
if err != nil {
t.Fatalf("unable to create htlc output: %v", err)
}
anchorOutput := &wire.TxOut{
PkScript: anchorPkScript,
Value: int64(anchorSize),
}
// Create mock signer for Alice.
aliceSigner := &MockSigner{Privkeys: []*btcec.PrivateKey{aliceKeyPriv}}
testCases := []struct {
witness func() wire.TxWitness
valid bool
}{
{
// Alice can spend immediately.
makeWitnessTestCase(t, func() (wire.TxWitness, error) {
sweepTx.TxIn[0].Sequence = wire.MaxTxInSequenceNum
sweepTxSigHashes := txscript.NewTxSigHashes(sweepTx)
signDesc := &SignDescriptor{
KeyDesc: keychain.KeyDescriptor{
PubKey: aliceKeyPub,
},
WitnessScript: anchorScript,
Output: anchorOutput,
HashType: txscript.SigHashAll,
SigHashes: sweepTxSigHashes,
InputIndex: 0,
}
return CommitSpendAnchor(aliceSigner, signDesc,
sweepTx)
}),
true,
},
{
// Anyone can spend after 16 blocks.
makeWitnessTestCase(t, func() (wire.TxWitness, error) {
sweepTx.TxIn[0].Sequence = LockTimeToSequence(false, 16)
return CommitSpendAnchorAnyone(anchorScript)
}),
true,
},
{
// Anyone cannot spend before 16 blocks.
makeWitnessTestCase(t, func() (wire.TxWitness, error) {
sweepTx.TxIn[0].Sequence = LockTimeToSequence(false, 15)
return CommitSpendAnchorAnyone(anchorScript)
}),
false,
},
}
for i, testCase := range testCases {
sweepTx.TxIn[0].Witness = testCase.witness()
newEngine := func() (*txscript.Engine, error) {
return txscript.NewEngine(anchorPkScript,
sweepTx, 0, txscript.StandardVerifyFlags, nil,
nil, int64(anchorSize))
}
assertEngineExecution(t, i, testCase.valid, newEngine)
}
}

@ -132,6 +132,12 @@ const (
// - PkScript (P2WPKH)
CommitmentKeyHashOutput = 8 + 1 + P2WPKHSize
// CommitmentAnchorOutput 43 bytes
// - Value: 8 bytes
// - VarInt: 1 byte (PkScript length)
// - PkScript (P2WSH)
CommitmentAnchorOutput = 8 + 1 + P2WSHSize
// HTLCSize 43 bytes
// - Value: 8 bytes
// - VarInt: 1 byte (PkScript length)
@ -169,9 +175,32 @@ const (
// WitnessCommitmentTxWeight 224 weight
WitnessCommitmentTxWeight = WitnessHeaderSize + WitnessSize
// BaseAnchorCommitmentTxSize 225 + 43 * num-htlc-outputs bytes
// - Version: 4 bytes
// - WitnessHeader <---- part of the witness data
// - CountTxIn: 1 byte
// - TxIn: 41 bytes
// FundingInput
// - CountTxOut: 3 byte
// - TxOut: 4*43 + 43 * num-htlc-outputs bytes
// OutputPayingToThem,
// OutputPayingToUs,
// AnchorPayingToThem,
// AnchorPayingToUs,
// ....HTLCOutputs...
// - LockTime: 4 bytes
BaseAnchorCommitmentTxSize = 4 + 1 + FundingInputSize + 3 +
2*CommitmentDelayOutput + 2*CommitmentAnchorOutput + 4
// BaseAnchorCommitmentTxWeight 900 weight
BaseAnchorCommitmentTxWeight = witnessScaleFactor * BaseAnchorCommitmentTxSize
// CommitWeight 724 weight
CommitWeight = BaseCommitmentTxWeight + WitnessCommitmentTxWeight
// AnchorCommitWeight 1124 weight
AnchorCommitWeight = BaseAnchorCommitmentTxWeight + WitnessCommitmentTxWeight
// HTLCWeight 172 weight
HTLCWeight = witnessScaleFactor * HTLCSize
@ -183,6 +212,23 @@ const (
// which will transition an incoming HTLC to the delay-and-claim state.
HtlcSuccessWeight = 703
// HtlcConfirmedScriptOverhead is the extra length of an HTLC script
// that requires confirmation before it can be spent. These extra bytes
// is a result of the extra CSV check.
HtlcConfirmedScriptOverhead = 3
// HtlcTimeoutWeightConfirmed is the weight of the HTLC timeout
// transaction which will transition an outgoing HTLC to the
// delay-and-claim state, for the confirmed HTLC outputs. It is 3 bytes
// larger because of the additional CSV check in the input script.
HtlcTimeoutWeightConfirmed = HtlcTimeoutWeight + HtlcConfirmedScriptOverhead
// HtlcSuccessWeightCOnfirmed is the weight of the HTLC success
// transaction which will transition an incoming HTLC to the
// delay-and-claim state, for the confirmed HTLC outputs. It is 3 bytes
// larger because of the cdditional CSV check in the input script.
HtlcSuccessWeightConfirmed = HtlcSuccessWeight + HtlcConfirmedScriptOverhead
// MaxHTLCNumber is the maximum number HTLCs which can be included in a
// commitment transaction. This limit was chosen such that, in the case
// of a contract breach, the punishment transaction is able to sweep
@ -223,7 +269,23 @@ const (
// - witness_script (to_local_script)
ToLocalPenaltyWitnessSize = 1 + 1 + 73 + 1 + 1 + ToLocalScriptSize
// AcceptedHtlcScriptSize 139 bytes
// ToRemoteConfirmedScriptSize 37 bytes
// - OP_DATA: 1 byte
// - to_remote_key: 33 bytes
// - OP_CHECKSIGVERIFY: 1 byte
// - OP_1: 1 byte
// - OP_CHECKSEQUENCEVERIFY: 1 byte
ToRemoteConfirmedScriptSize = 1 + 33 + 1 + 1 + 1
// ToRemoteConfirmedWitnessSize 113 bytes
// - number_of_witness_elements: 1 byte
// - sig_length: 1 byte
// - sig: 73 bytes
// - witness_script_length: 1 byte
// - witness_script (to_remote_delayed_script)
ToRemoteConfirmedWitnessSize = 1 + 1 + 73 + 1 + ToRemoteConfirmedScriptSize
// AcceptedHtlcScriptSize 142 bytes
// - OP_DUP: 1 byte
// - OP_HASH160: 1 byte
// - OP_DATA: 1 byte (RIPEMD160(SHA256(revocationkey)) length)
@ -257,11 +319,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
@ -270,20 +335,7 @@ const (
// - witness_script: (accepted_htlc_script)
AcceptedHtlcTimeoutWitnessSize = 1 + 1 + 73 + 1 + 1 + AcceptedHtlcScriptSize
// AcceptedHtlcSuccessWitnessSize 322 bytes
// - number_of_witness_elements: 1 byte
// - nil_length: 1 byte
// - sig_alice_length: 1 byte
// - sig_alice: 73 bytes
// - sig_bob_length: 1 byte
// - sig_bob: 73 bytes
// - preimage_length: 1 byte
// - preimage: 32 bytes
// - witness_script_length: 1 byte
// - witness_script (accepted_htlc_script)
AcceptedHtlcSuccessWitnessSize = 1 + 1 + 73 + 1 + 73 + 1 + 32 + 1 + AcceptedHtlcScriptSize
// AcceptedHtlcPenaltyWitnessSize 249 bytes
// AcceptedHtlcPenaltyWitnessSize 252 bytes
// - number_of_witness_elements: 1 byte
// - revocation_sig_length: 1 byte
// - revocation_sig: 73 bytes
@ -293,7 +345,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)
@ -324,22 +376,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
// OfferedHtlcTimeoutWitnessSize 285 bytes
// - number_of_witness_elements: 1 byte
// - nil_length: 1 byte
// - sig_alice_length: 1 byte
// - sig_alice: 73 bytes
// - sig_bob_length: 1 byte
// - sig_bob: 73 bytes
// - nil_length: 1 byte
// - witness_script_length: 1 byte
// - witness_script (offered_htlc_script)
OfferedHtlcTimeoutWitnessSize = 1 + 1 + 1 + 73 + 1 + 73 + 1 + 1 + OfferedHtlcScriptSize
// OfferedHtlcSuccessWitnessSize 317 bytes
// OfferedHtlcSuccessWitnessSize 320 bytes
// - number_of_witness_elements: 1 byte
// - nil_length: 1 byte
// - receiver_sig_length: 1 byte
@ -352,7 +395,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

@ -67,7 +67,7 @@ func (m *MockSigner) SignOutputRaw(tx *wire.MsgTx, signDesc *SignDescriptor) ([]
sig, err := txscript.RawTxInWitnessSignature(tx, signDesc.SigHashes,
signDesc.InputIndex, signDesc.Output.Value, signDesc.WitnessScript,
txscript.SigHashAll, privKey)
signDesc.HashType, privKey)
if err != nil {
return nil, err
}

@ -48,8 +48,9 @@ type StandardWitnessType uint16
var _ WitnessType = (StandardWitnessType)(0)
const (
// CommitmentTimeLock is a witness that allows us to spend the output of
// a commitment transaction after a relative lock-time lockout.
// CommitmentTimeLock is a witness that allows us to spend our output
// on our local commitment transaction after a relative lock-time
// lockout.
CommitmentTimeLock StandardWitnessType = 0
// CommitmentNoDelay is a witness that allows us to spend a settled
@ -119,6 +120,11 @@ const (
// type, but it omits the tweak that randomizes the key we need to
// spend with a channel peer supplied set of randomness.
CommitSpendNoDelayTweakless StandardWitnessType = 12
// CommitmentToRemoteConfirmed is a witness that allows us to spend our
// output on the counterparty's commitment transaction after a
// confirmation.
CommitmentToRemoteConfirmed StandardWitnessType = 13
)
// String returns a human readable version of the target WitnessType.
@ -129,6 +135,9 @@ func (wt StandardWitnessType) String() string {
case CommitmentTimeLock:
return "CommitmentTimeLock"
case CommitmentToRemoteConfirmed:
return "CommitmentToRemoteConfirmed"
case CommitmentNoDelay:
return "CommitmentNoDelay"
@ -197,6 +206,18 @@ func (wt StandardWitnessType) WitnessGenerator(signer Signer,
Witness: witness,
}, nil
case CommitmentToRemoteConfirmed:
witness, err := CommitSpendToRemoteConfirmed(
signer, desc, tx,
)
if err != nil {
return nil, err
}
return &Script{
Witness: witness,
}, nil
case CommitmentNoDelay:
witness, err := CommitSpendNoDelay(signer, desc, tx, false)
if err != nil {
@ -323,6 +344,10 @@ func (wt StandardWitnessType) SizeUpperBound() (int, bool, error) {
case CommitmentTimeLock:
return ToLocalTimeoutWitnessSize, false, nil
// 1 CSV time locked output to us on remote commitment.
case CommitmentToRemoteConfirmed:
return ToRemoteConfirmedWitnessSize, false, nil
// Outgoing second layer HTLC's that have confirmed within the
// chain, and the output they produced is now mature enough to
// sweep.

@ -2,21 +2,27 @@
package lncfg
// LegacyProtocol is a struct that we use to be able to test backwards
// ProtocolOptions is a struct that we use to be able to test backwards
// compatibility of protocol additions, while defaulting to the latest within
// lnd.
type LegacyProtocol struct {
// lnd, or to enable experimental protocol changes.
type ProtocolOptions struct {
}
// LegacyOnion returns true if the old legacy onion format should be used when
// we're an intermediate or final hop. This controls if we set the
// TLVOnionPayloadOptional bit or not.
func (l *LegacyProtocol) LegacyOnion() bool {
func (l *ProtocolOptions) LegacyOnion() bool {
return false
}
// LegacyOnion returns true if the old commitment format should be used for new
// funded channels.
func (l *LegacyProtocol) LegacyCommitment() bool {
// NoStaticRemoteKey returns true if the old commitment format with a tweaked
// remote key should be used for new funded channels.
func (l *ProtocolOptions) NoStaticRemoteKey() bool {
return false
}
// AnchorCommitments returns true if support for the the anchor commitment type
// should be signaled.
func (l *ProtocolOptions) AnchorCommitments() bool {
return false
}

@ -2,31 +2,41 @@
package lncfg
// LegacyProtocol is a struct that we use to be able to test backwards
// ProtocolOptions is a struct that we use to be able to test backwards
// compatibility of protocol additions, while defaulting to the latest within
// lnd.
type LegacyProtocol struct {
// Onion if set to true, then we won't signal TLVOnionPayloadOptional.
// As a result, nodes that include us in the route won't use the new
// modern onion framing.
Onion bool `long:"onion" description:"force node to not advertise the new modern TLV onion format"`
// lnd, or to enable experimental protocol changes.
type ProtocolOptions struct {
// LegacyOnionFormat if set to true, then we won't signal
// TLVOnionPayloadOptional. As a result, nodes that include us in the
// route won't use the new modern onion framing.
LegacyOnionFormat bool `long:"legacyonion" description:"force node to not advertise the new modern TLV onion format"`
// CommitmentTweak guards if we should use the old legacy commitment
// protocol, or the newer variant that doesn't have a tweak for the
// remote party's output in the commitment. If set to true, then we
// won't signal StaticRemoteKeyOptional.
CommitmentTweak bool `long:"committweak" description:"force node to not advertise the new commitment format"`
// Anchors should be set if we want to support opening or accepting
// channels having the anchor commitment type.
Anchors bool `long:"anchors" description:"EXPERIMENTAL: enable experimental support for anchor commitments. Won't work with watchtowers or static channel backups"`
}
// LegacyOnion returns true if the old legacy onion format should be used when
// we're an intermediate or final hop. This controls if we set the
// TLVOnionPayloadOptional bit or not.
func (l *LegacyProtocol) LegacyOnion() bool {
return l.Onion
func (l *ProtocolOptions) LegacyOnion() bool {
return l.LegacyOnionFormat
}
// LegacyOnion returns true if the old commitment format should be used for new
// funded channels.
func (l *LegacyProtocol) LegacyCommitment() bool {
// NoStaticRemoteKey returns true if the old commitment format with a tweaked
// remote key should be used for new funded channels.
func (l *ProtocolOptions) NoStaticRemoteKey() bool {
return l.CommitmentTweak
}
// AnchorCommitments returns true if support for the the anchor commitment type
// should be signaled.
func (l *ProtocolOptions) AnchorCommitments() bool {
return l.Anchors
}

@ -953,6 +953,43 @@ func testOnchainFundRecovery(net *lntest.NetworkHarness, t *harnessTest) {
restoreCheckBalance(6*btcutil.SatoshiPerBitcoin, 6, 20, nil)
}
// commitType is a simple enum used to run though the basic funding flow with
// different commitment formats.
type commitType byte
const (
// commitTypeLegacy is the old school commitment type.
commitTypeLegacy = iota
// commiTypeTweakless is the commitment type where the remote key is
// static (non-tweaked).
commitTypeTweakless
)
// String returns that name of the commitment type.
func (c commitType) String() string {
switch c {
case commitTypeLegacy:
return "legacy"
case commitTypeTweakless:
return "tweakless"
default:
return "invalid"
}
}
// Args returns the command line flag to supply to enable this commitment type.
func (c commitType) Args() []string {
switch c {
case commitTypeLegacy:
return []string{"--protocol.committweak"}
case commitTypeTweakless:
return []string{}
}
return nil
}
// basicChannelFundingTest is a sub-test of the main testBasicChannelFunding
// test. Given two nodes: Alice and Bob, it'll assert proper channel creation,
// then return a function closure that should be called to assert proper
@ -1049,19 +1086,23 @@ func testBasicChannelFunding(net *lntest.NetworkHarness, t *harnessTest) {
ctxb := context.Background()
// Run through the test with combinations of all the different
// commitment types.
allTypes := []commitType{
commitTypeLegacy,
commitTypeTweakless,
}
test:
// We'll test all possible combinations of the feature bit presence
// that both nodes can signal for this new channel type. We'll make a
// new Carol+Dave for each test instance as well.
for _, carolTweakless := range []bool{true, false} {
for _, daveTweakless := range []bool{true, false} {
for _, carolCommitType := range allTypes {
for _, daveCommitType := range allTypes {
// Based on the current tweak variable for Carol, we'll
// preferentially signal the legacy commitment format.
// We do the same for Dave shortly below.
var carolArgs []string
if !carolTweakless {
carolArgs = []string{"--legacyprotocol.committweak"}
}
carolArgs := carolCommitType.Args()
carol, err := net.NewNode("Carol", carolArgs)
if err != nil {
t.Fatalf("unable to create new node: %v", err)
@ -1075,10 +1116,7 @@ test:
t.Fatalf("unable to send coins to carol: %v", err)
}
var daveArgs []string
if !daveTweakless {
daveArgs = []string{"--legacyprotocol.committweak"}
}
daveArgs := daveCommitType.Args()
dave, err := net.NewNode("Dave", daveArgs)
if err != nil {
t.Fatalf("unable to create new node: %v", err)
@ -1093,8 +1131,8 @@ test:
t.Fatalf("unable to connect peers: %v", err)
}
testName := fmt.Sprintf("carol_tweak=%v,dave_tweak=%v",
carolTweakless, daveTweakless)
testName := fmt.Sprintf("carol_commit=%v,dave_commit=%v",
carolCommitType, daveCommitType)
ht := t
success := t.t.Run(testName, func(t *testing.T) {
@ -1105,6 +1143,10 @@ test:
t.Fatalf("failed funding flow: %v", err)
}
carolTweakless := carolCommitType == commitTypeTweakless
daveTweakless := daveCommitType == commitTypeTweakless
tweaklessSignalled := carolTweakless && daveTweakless
tweaklessChans := (carolChannel.StaticRemoteKey &&
daveChannel.StaticRemoteKey)
@ -4205,7 +4247,7 @@ func testMultiHopPayments(net *lntest.NetworkHarness, t *harnessTest) {
//
// First, we'll create Dave and establish a channel to Alice. Dave will
// be running an older node that requires the legacy onion payload.
daveArgs := []string{"--legacyprotocol.onion"}
daveArgs := []string{"--protocol.legacyonion"}
dave, err := net.NewNode("Dave", daveArgs)
if err != nil {
t.Fatalf("unable to create new nodes: %v", err)

@ -503,7 +503,8 @@ type commitment struct {
// evaluating all the add/remove/settle log entries before the listed
// indexes.
//
// NOTE: This is the balance *after* subtracting any commitment fee.
// NOTE: This is the balance *after* subtracting any commitment fee,
// AND anchor output values.
ourBalance lnwire.MilliSatoshi
theirBalance lnwire.MilliSatoshi
@ -595,7 +596,7 @@ func locateOutputIndex(p *PaymentDescriptor, tx *wire.MsgTx, ourCommit bool,
// we need to keep track of the indexes of each HTLC in order to properly write
// the current state to disk, and also to locate the PaymentDescriptor
// corresponding to HTLC outputs in the commitment transaction.
func (c *commitment) populateHtlcIndexes() error {
func (c *commitment) populateHtlcIndexes(chanType channeldb.ChannelType) error {
// First, we'll set up some state to allow us to locate the output
// index of the all the HTLC's within the commitment transaction. We
// must keep this index so we can validate the HTLC signatures sent to
@ -607,8 +608,10 @@ func (c *commitment) populateHtlcIndexes() error {
// populateIndex is a helper function that populates the necessary
// indexes within the commitment view for a particular HTLC.
populateIndex := func(htlc *PaymentDescriptor, incoming bool) error {
isDust := htlcIsDust(incoming, c.isOurs, c.feePerKw,
htlc.Amount.ToSatoshis(), c.dustLimit)
isDust := htlcIsDust(
chanType, incoming, c.isOurs, c.feePerKw,
htlc.Amount.ToSatoshis(), c.dustLimit,
)
var err error
switch {
@ -773,6 +776,7 @@ func (lc *LightningChannel) diskHtlcToPayDesc(feeRate chainfee.SatPerKWeight,
ourWitnessScript, theirWitnessScript []byte
pd PaymentDescriptor
err error
chanType = lc.channelState.ChanType
)
// If the either outputs is dust from the local or remote node's
@ -780,22 +784,28 @@ func (lc *LightningChannel) diskHtlcToPayDesc(feeRate chainfee.SatPerKWeight,
// generate them in order to locate the outputs within the commitment
// transaction. As we'll mark dust with a special output index in the
// on-disk state snapshot.
isDustLocal := htlcIsDust(htlc.Incoming, true, feeRate,
htlc.Amt.ToSatoshis(), lc.channelState.LocalChanCfg.DustLimit)
isDustLocal := htlcIsDust(
chanType, htlc.Incoming, true, feeRate,
htlc.Amt.ToSatoshis(), lc.channelState.LocalChanCfg.DustLimit,
)
if !isDustLocal && localCommitKeys != nil {
ourP2WSH, ourWitnessScript, err = genHtlcScript(
htlc.Incoming, true, htlc.RefundTimeout, htlc.RHash,
localCommitKeys)
chanType, htlc.Incoming, true, htlc.RefundTimeout,
htlc.RHash, localCommitKeys,
)
if err != nil {
return pd, err
}
}
isDustRemote := htlcIsDust(htlc.Incoming, false, feeRate,
htlc.Amt.ToSatoshis(), lc.channelState.RemoteChanCfg.DustLimit)
isDustRemote := htlcIsDust(
chanType, htlc.Incoming, false, feeRate,
htlc.Amt.ToSatoshis(), lc.channelState.RemoteChanCfg.DustLimit,
)
if !isDustRemote && remoteCommitKeys != nil {
theirP2WSH, theirWitnessScript, err = genHtlcScript(
htlc.Incoming, false, htlc.RefundTimeout, htlc.RHash,
remoteCommitKeys)
chanType, htlc.Incoming, false, htlc.RefundTimeout,
htlc.RHash, remoteCommitKeys,
)
if err != nil {
return pd, err
}
@ -926,7 +936,8 @@ func (lc *LightningChannel) diskCommitToMemCommit(isLocal bool,
// Finally, we'll re-populate the HTLC index for this state so we can
// properly locate each HTLC within the commitment transaction.
if err := commit.populateHtlcIndexes(); err != nil {
err = commit.populateHtlcIndexes(lc.channelState.ChanType)
if err != nil {
return nil, err
}
@ -1406,11 +1417,14 @@ func (lc *LightningChannel) logUpdateToPayDesc(logUpdate *channeldb.LogUpdate,
pd.OnionBlob = make([]byte, len(wireMsg.OnionBlob))
copy(pd.OnionBlob[:], wireMsg.OnionBlob[:])
isDustRemote := htlcIsDust(false, false, feeRate,
wireMsg.Amount.ToSatoshis(), remoteDustLimit)
isDustRemote := htlcIsDust(
lc.channelState.ChanType, false, false, feeRate,
wireMsg.Amount.ToSatoshis(), remoteDustLimit,
)
if !isDustRemote {
theirP2WSH, theirWitnessScript, err := genHtlcScript(
false, false, wireMsg.Expiry, wireMsg.PaymentHash,
lc.channelState.ChanType, false, false,
wireMsg.Expiry, wireMsg.PaymentHash,
remoteCommitKeys,
)
if err != nil {
@ -2008,6 +2022,10 @@ type BreachRetribution struct {
// party) within the breach transaction.
LocalOutpoint wire.OutPoint
// LocalDelay is the CSV delay for the to_remote script on the breached
// commitment.
LocalDelay uint32
// RemoteOutputSignDesc is a SignDescriptor which is capable of
// generating the signature required to claim the funds as described
// within the revocation clause of the remote party's commitment
@ -2021,6 +2039,10 @@ type BreachRetribution struct {
// party within the breach transaction.
RemoteOutpoint wire.OutPoint
// RemoteDelay specifies the CSV delay applied to to-local scripts on
// the breaching commitment transaction.
RemoteDelay uint32
// HtlcRetributions is a slice of HTLC retributions for each output
// active HTLC output within the breached commitment transaction.
HtlcRetributions []HtlcRetribution
@ -2029,10 +2051,6 @@ type BreachRetribution struct {
// breaching commitment transaction. This allows downstream clients to
// have access to the public keys used in the scripts.
KeyRing *CommitmentKeyRing
// RemoteDelay specifies the CSV delay applied to to-local scripts on
// the breaching commitment transaction.
RemoteDelay uint32
}
// NewBreachRetribution creates a new fully populated BreachRetribution for the
@ -2085,9 +2103,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.
ourDelay := uint32(chanState.LocalChanCfg.CsvDelay)
ourScript, err := CommitScriptToRemote(
chanState.ChanType, ourDelay, keyRing.ToRemoteKey,
ourScript, ourDelay, err := CommitScriptToRemote(
chanState.ChanType, keyRing.ToRemoteKey,
)
if err != nil {
return nil, err
@ -2157,15 +2174,10 @@ func NewBreachRetribution(chanState *channeldb.OpenChannel, stateNum uint64,
// remote commitment transaction.
htlcRetributions := make([]HtlcRetribution, 0, len(revokedSnapshot.Htlcs))
for _, htlc := range revokedSnapshot.Htlcs {
var (
htlcWitnessScript []byte
err error
)
// If the HTLC is dust, then we'll skip it as it doesn't have
// an output on the commitment transaction.
if htlcIsDust(
htlc.Incoming, false,
chanState.ChanType, htlc.Incoming, false,
chainfee.SatPerKWeight(revokedSnapshot.FeePerKw),
htlc.Amt.ToSatoshis(), chanState.RemoteChanCfg.DustLimit,
) {
@ -2185,31 +2197,13 @@ func NewBreachRetribution(chanState *channeldb.OpenChannel, stateNum uint64,
// If this is an incoming HTLC, then this means that they were
// the sender of the HTLC (relative to us). So we'll
// re-generate the sender HTLC script.
if htlc.Incoming {
htlcWitnessScript, err = input.SenderHTLCScript(
keyRing.RemoteHtlcKey, keyRing.LocalHtlcKey,
keyRing.RevocationKey, htlc.RHash[:],
)
if err != nil {
return nil, err
}
} else {
// Otherwise, is this was an outgoing HTLC that we
// sent, then from the PoV of the remote commitment
// state, they're the receiver of this HTLC.
htlcWitnessScript, err = input.ReceiverHTLCScript(
htlc.RefundTimeout, keyRing.LocalHtlcKey,
keyRing.RemoteHtlcKey, keyRing.RevocationKey,
htlc.RHash[:],
)
if err != nil {
return nil, err
}
}
htlcPkScript, err := input.WitnessScriptHash(htlcWitnessScript)
// re-generate the sender HTLC script. Otherwise, is this was
// an outgoing HTLC that we sent, then from the PoV of the
// remote commitment state, they're the receiver of this HTLC.
htlcPkScript, htlcWitnessScript, err := genHtlcScript(
chanState.ChanType, htlc.Incoming, false,
htlc.RefundTimeout, htlc.RHash, keyRing,
)
if err != nil {
return nil, err
}
@ -2245,33 +2239,23 @@ func NewBreachRetribution(chanState *channeldb.OpenChannel, stateNum uint64,
PendingHTLCs: revokedSnapshot.Htlcs,
LocalOutpoint: ourOutpoint,
LocalOutputSignDesc: ourSignDesc,
LocalDelay: ourDelay,
RemoteOutpoint: theirOutpoint,
RemoteOutputSignDesc: theirSignDesc,
RemoteDelay: theirDelay,
HtlcRetributions: htlcRetributions,
KeyRing: keyRing,
RemoteDelay: theirDelay,
}, nil
}
// htlcTimeoutFee returns the fee in satoshis required for an HTLC timeout
// transaction based on the current fee rate.
func htlcTimeoutFee(feePerKw chainfee.SatPerKWeight) btcutil.Amount {
return feePerKw.FeeForWeight(input.HtlcTimeoutWeight)
}
// htlcSuccessFee returns the fee in satoshis required for an HTLC success
// transaction based on the current fee rate.
func htlcSuccessFee(feePerKw chainfee.SatPerKWeight) btcutil.Amount {
return feePerKw.FeeForWeight(input.HtlcSuccessWeight)
}
// htlcIsDust determines if an HTLC output is dust or not depending on two
// bits: if the HTLC is incoming and if the HTLC will be placed on our
// commitment transaction, or theirs. These two pieces of information are
// require as we currently used second-level HTLC transactions as off-chain
// covenants. Depending on the two bits, we'll either be using a timeout or
// success transaction which have different weights.
func htlcIsDust(incoming, ourCommit bool, feePerKw chainfee.SatPerKWeight,
func htlcIsDust(chanType channeldb.ChannelType,
incoming, ourCommit bool, feePerKw chainfee.SatPerKWeight,
htlcAmt, dustLimit btcutil.Amount) bool {
// First we'll determine the fee required for this HTLC based on if this is
@ -2283,25 +2267,25 @@ func htlcIsDust(incoming, ourCommit bool, feePerKw chainfee.SatPerKWeight,
// If this is an incoming HTLC on our commitment transaction, then the
// second-level transaction will be a success transaction.
case incoming && ourCommit:
htlcFee = htlcSuccessFee(feePerKw)
htlcFee = HtlcSuccessFee(chanType, feePerKw)
// If this is an incoming HTLC on their commitment transaction, then
// we'll be using a second-level timeout transaction as they've added
// this HTLC.
case incoming && !ourCommit:
htlcFee = htlcTimeoutFee(feePerKw)
htlcFee = HtlcTimeoutFee(chanType, feePerKw)
// If this is an outgoing HTLC on our commitment transaction, then
// we'll be using a timeout transaction as we're the sender of the
// HTLC.
case !incoming && ourCommit:
htlcFee = htlcTimeoutFee(feePerKw)
htlcFee = HtlcTimeoutFee(chanType, feePerKw)
// If this is an outgoing HTLC on their commitment transaction, then
// we'll be using an HTLC success transaction as they're the receiver
// of this HTLC.
case !incoming && !ourCommit:
htlcFee = htlcSuccessFee(feePerKw)
htlcFee = HtlcSuccessFee(chanType, feePerKw)
}
return (htlcAmt - htlcFee) < dustLimit
@ -2391,6 +2375,29 @@ func (lc *LightningChannel) fetchCommitmentView(remoteChain bool,
return nil, err
}
// We'll assert that there hasn't been a mistake during fee calculation
// leading to a fee too low.
var totalOut btcutil.Amount
for _, txOut := range commitTx.txn.TxOut {
totalOut += btcutil.Amount(txOut.Value)
}
fee := lc.channelState.Capacity - totalOut
// Since the transaction is not signed yet, we use the witness weight
// used for weight calculation.
uTx := btcutil.NewTx(commitTx.txn)
weight := blockchain.GetTransactionWeight(uTx) +
input.WitnessCommitmentTxWeight
effFeeRate := chainfee.SatPerKWeight(fee) * 1000 /
chainfee.SatPerKWeight(weight)
if effFeeRate < chainfee.FeePerKwFloor {
return nil, fmt.Errorf("height=%v, for ChannelPoint(%v) "+
"attempts to create commitment wigh feerate %v: %v",
nextHeight, lc.channelState.FundingOutpoint,
effFeeRate, spew.Sdump(commitTx))
}
// With the commitment view created, store the resulting balances and
// transaction with the other parameters for this height.
c := &commitment{
@ -2422,7 +2429,7 @@ func (lc *LightningChannel) fetchCommitmentView(remoteChain bool,
// Finally, we'll populate all the HTLC indexes so we can track the
// locations of each HTLC in the commitment state.
if err := c.populateHtlcIndexes(); err != nil {
if err := c.populateHtlcIndexes(lc.channelState.ChanType); err != nil {
return nil, err
}
@ -2737,12 +2744,14 @@ 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,
remoteCommitView *commitment) ([]SignJob, chan struct{}, error) {
txHash := remoteCommitView.txn.TxHash()
dustLimit := remoteChanCfg.DustLimit
feePerKw := remoteCommitView.feePerKw
sigHashType := HtlcSigHashType(chanType)
// With the keys generated, we'll make a slice with enough capacity to
// hold potentially all the HTLCs. The actual slice may be a bit
@ -2758,8 +2767,10 @@ func genRemoteHtlcSigJobs(keyRing *CommitmentKeyRing,
// dust output after taking into account second-level HTLC fees, then a
// sigJob will be generated and appended to the current batch.
for _, htlc := range remoteCommitView.incomingHTLCs {
if htlcIsDust(true, false, feePerKw, htlc.Amount.ToSatoshis(),
dustLimit) {
if htlcIsDust(
chanType, true, false, feePerKw,
htlc.Amount.ToSatoshis(), dustLimit,
) {
continue
}
@ -2774,7 +2785,7 @@ func genRemoteHtlcSigJobs(keyRing *CommitmentKeyRing,
// HTLC timeout transaction for them. The output of the timeout
// transaction needs to account for fees, so we'll compute the
// required fee and output now.
htlcFee := htlcTimeoutFee(feePerKw)
htlcFee := HtlcTimeoutFee(chanType, feePerKw)
outputAmt := htlc.Amount.ToSatoshis() - htlcFee
// With the fee calculate, we can properly create the HTLC
@ -2784,7 +2795,7 @@ func genRemoteHtlcSigJobs(keyRing *CommitmentKeyRing,
Index: uint32(htlc.remoteOutputIndex),
}
sigJob.Tx, err = createHtlcTimeoutTx(
op, outputAmt, htlc.Timeout,
chanType, op, outputAmt, htlc.Timeout,
uint32(remoteChanCfg.CsvDelay),
keyRing.RevocationKey, keyRing.ToLocalKey,
)
@ -2802,7 +2813,7 @@ func genRemoteHtlcSigJobs(keyRing *CommitmentKeyRing,
Output: &wire.TxOut{
Value: int64(htlc.Amount.ToSatoshis()),
},
HashType: txscript.SigHashAll,
HashType: sigHashType,
SigHashes: txscript.NewTxSigHashes(sigJob.Tx),
InputIndex: 0,
}
@ -2811,8 +2822,10 @@ func genRemoteHtlcSigJobs(keyRing *CommitmentKeyRing,
sigBatch = append(sigBatch, sigJob)
}
for _, htlc := range remoteCommitView.outgoingHTLCs {
if htlcIsDust(false, false, feePerKw, htlc.Amount.ToSatoshis(),
dustLimit) {
if htlcIsDust(
chanType, false, false, feePerKw,
htlc.Amount.ToSatoshis(), dustLimit,
) {
continue
}
@ -2825,7 +2838,7 @@ func genRemoteHtlcSigJobs(keyRing *CommitmentKeyRing,
// HTLC success transaction for them. The output of the timeout
// transaction needs to account for fees, so we'll compute the
// required fee and output now.
htlcFee := htlcSuccessFee(feePerKw)
htlcFee := HtlcSuccessFee(chanType, feePerKw)
outputAmt := htlc.Amount.ToSatoshis() - htlcFee
// With the proper output amount calculated, we can now
@ -2836,7 +2849,7 @@ func genRemoteHtlcSigJobs(keyRing *CommitmentKeyRing,
Index: uint32(htlc.remoteOutputIndex),
}
sigJob.Tx, err = createHtlcSuccessTx(
op, outputAmt, uint32(remoteChanCfg.CsvDelay),
chanType, op, outputAmt, uint32(remoteChanCfg.CsvDelay),
keyRing.RevocationKey, keyRing.ToLocalKey,
)
if err != nil {
@ -2853,7 +2866,7 @@ func genRemoteHtlcSigJobs(keyRing *CommitmentKeyRing,
Output: &wire.TxOut{
Value: int64(htlc.Amount.ToSatoshis()),
},
HashType: txscript.SigHashAll,
HashType: sigHashType,
SigHashes: txscript.NewTxSigHashes(sigJob.Tx),
InputIndex: 0,
}
@ -3341,7 +3354,8 @@ 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.
sigBatch, cancelChan, err := genRemoteHtlcSigJobs(keyRing,
sigBatch, cancelChan, err := genRemoteHtlcSigJobs(
keyRing, lc.channelState.ChanType,
&lc.channelState.LocalChanCfg, &lc.channelState.RemoteChanCfg,
newCommitView,
)
@ -3773,23 +3787,28 @@ func (lc *LightningChannel) computeView(view *htlcView, remoteChain bool,
// weight, needed to calculate the transaction fee.
var totalHtlcWeight int64
for _, htlc := range filteredHTLCView.ourUpdates {
if htlcIsDust(remoteChain, !remoteChain, feePerKw,
htlc.Amount.ToSatoshis(), dustLimit) {
if htlcIsDust(
lc.channelState.ChanType, remoteChain, !remoteChain,
feePerKw, htlc.Amount.ToSatoshis(), dustLimit,
) {
continue
}
totalHtlcWeight += input.HTLCWeight
}
for _, htlc := range filteredHTLCView.theirUpdates {
if htlcIsDust(!remoteChain, !remoteChain, feePerKw,
htlc.Amount.ToSatoshis(), dustLimit) {
if htlcIsDust(
lc.channelState.ChanType, !remoteChain, !remoteChain,
feePerKw, htlc.Amount.ToSatoshis(), dustLimit,
) {
continue
}
totalHtlcWeight += input.HTLCWeight
}
totalCommitWeight := input.CommitWeight + totalHtlcWeight
totalCommitWeight := CommitWeight(lc.channelState.ChanType) +
totalHtlcWeight
return ourBalance, theirBalance, totalCommitWeight, filteredHTLCView, nil
}
@ -3799,10 +3818,12 @@ 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,
localChanCfg, remoteChanCfg *channeldb.ChannelConfig) ([]VerifyJob, error) {
txHash := localCommitmentView.txn.TxHash()
feePerKw := localCommitmentView.feePerKw
sigHashType := HtlcSigHashType(chanType)
// With the required state generated, we'll create a slice with large
// enough capacity to hold verification jobs for all HTLC's in this
@ -3842,12 +3863,14 @@ func genHtlcSigValidationJobs(localCommitmentView *commitment,
Index: uint32(htlc.localOutputIndex),
}
htlcFee := htlcSuccessFee(feePerKw)
htlcFee := HtlcSuccessFee(chanType, feePerKw)
outputAmt := htlc.Amount.ToSatoshis() - htlcFee
successTx, err := createHtlcSuccessTx(op,
outputAmt, uint32(localChanCfg.CsvDelay),
keyRing.RevocationKey, keyRing.ToLocalKey)
successTx, err := createHtlcSuccessTx(
chanType, op, outputAmt,
uint32(localChanCfg.CsvDelay),
keyRing.RevocationKey, keyRing.ToLocalKey,
)
if err != nil {
return nil, err
}
@ -3855,7 +3878,7 @@ func genHtlcSigValidationJobs(localCommitmentView *commitment,
hashCache := txscript.NewTxSigHashes(successTx)
sigHash, err := txscript.CalcWitnessSigHash(
htlc.ourWitnessScript, hashCache,
txscript.SigHashAll, successTx, 0,
sigHashType, successTx, 0,
int64(htlc.Amount.ToSatoshis()),
)
if err != nil {
@ -3894,11 +3917,11 @@ func genHtlcSigValidationJobs(localCommitmentView *commitment,
Index: uint32(htlc.localOutputIndex),
}
htlcFee := htlcTimeoutFee(feePerKw)
htlcFee := HtlcTimeoutFee(chanType, feePerKw)
outputAmt := htlc.Amount.ToSatoshis() - htlcFee
timeoutTx, err := createHtlcTimeoutTx(op,
outputAmt, htlc.Timeout,
timeoutTx, err := createHtlcTimeoutTx(
chanType, op, outputAmt, htlc.Timeout,
uint32(localChanCfg.CsvDelay),
keyRing.RevocationKey, keyRing.ToLocalKey,
)
@ -3909,7 +3932,7 @@ func genHtlcSigValidationJobs(localCommitmentView *commitment,
hashCache := txscript.NewTxSigHashes(timeoutTx)
sigHash, err := txscript.CalcWitnessSigHash(
htlc.ourWitnessScript, hashCache,
txscript.SigHashAll, timeoutTx, 0,
sigHashType, timeoutTx, 0,
int64(htlc.Amount.ToSatoshis()),
)
if err != nil {
@ -4115,7 +4138,8 @@ func (lc *LightningChannel) ReceiveNewCommitment(commitSig lnwire.Sig,
// generated, we'll submit these jobs to the worker pool.
verifyJobs, err := genHtlcSigValidationJobs(
localCommitmentView, keyRing, htlcSigs,
&lc.channelState.LocalChanCfg, &lc.channelState.RemoteChanCfg,
lc.channelState.ChanType, &lc.channelState.LocalChanCfg,
&lc.channelState.RemoteChanCfg,
)
if err != nil {
return err
@ -5040,8 +5064,8 @@ func (lc *LightningChannel) getSignedCommitTx() (*wire.MsgTx, error) {
}
// CommitOutputResolution carries the necessary information required to allow
// us to sweep our direct commitment output in the case that either party goes
// to chain.
// us to sweep our commitment output in the case that either party goes to
// chain.
type CommitOutputResolution struct {
// SelfOutPoint is the full outpoint that points to out pay-to-self
// output within the closing commitment transaction.
@ -5053,8 +5077,7 @@ type CommitOutputResolution struct {
// MaturityDelay is the relative time-lock, in blocks for all outputs
// that pay to the local party within the broadcast commitment
// transaction. This value will be non-zero iff, this output was on our
// commitment transaction.
// transaction.
MaturityDelay uint32
}
@ -5123,6 +5146,7 @@ func NewUnilateralCloseSummary(chanState *channeldb.OpenChannel, signer input.Si
chainfee.SatPerKWeight(remoteCommit.FeePerKw), false, signer,
remoteCommit.Htlcs, keyRing, &chanState.LocalChanCfg,
&chanState.RemoteChanCfg, *commitSpend.SpenderTxHash,
chanState.ChanType,
)
if err != nil {
return nil, fmt.Errorf("unable to create htlc "+
@ -5134,9 +5158,8 @@ func NewUnilateralCloseSummary(chanState *channeldb.OpenChannel, signer input.Si
// Before we can generate the proper sign descriptor, we'll need to
// locate the output index of our non-delayed output on the commitment
// transaction.
localDelay := uint32(chanState.LocalChanCfg.CsvDelay)
selfScript, err := CommitScriptToRemote(
chanState.ChanType, localDelay, keyRing.ToRemoteKey,
selfScript, maturityDelay, err := CommitScriptToRemote(
chanState.ChanType, keyRing.ToRemoteKey,
)
if err != nil {
return nil, fmt.Errorf("unable to create self commit "+
@ -5177,7 +5200,7 @@ func NewUnilateralCloseSummary(chanState *channeldb.OpenChannel, signer input.Si
},
HashType: txscript.SigHashAll,
},
MaturityDelay: 0,
MaturityDelay: maturityDelay,
}
}
@ -5241,7 +5264,8 @@ type IncomingHtlcResolution struct {
// pass after the SignedSuccessTx is confirmed in the chain before the
// output can be swept.
//
// NOTE: If SignedSuccessTx is nil, then this field isn't needed.
// NOTE: If SignedTimeoutTx is nil, then this field denotes the CSV
// delay needed to spend from the commitment transaction.
CsvDelay uint32
// ClaimOutpoint is the final outpoint that needs to be spent in order
@ -5281,7 +5305,8 @@ type OutgoingHtlcResolution struct {
// pass after the SignedTimeoutTx is confirmed in the chain before the
// output can be swept.
//
// NOTE: If SignedTimeoutTx is nil, then this field isn't needed.
// NOTE: If SignedTimeoutTx is nil, then this field denotes the CSV
// delay needed to spend from the commitment transaction.
CsvDelay uint32
// ClaimOutpoint is the final outpoint that needs to be spent in order
@ -5318,31 +5343,27 @@ func newOutgoingHtlcResolution(signer input.Signer,
localChanCfg *channeldb.ChannelConfig, commitHash chainhash.Hash,
htlc *channeldb.HTLC, keyRing *CommitmentKeyRing,
feePerKw chainfee.SatPerKWeight, csvDelay uint32,
localCommit bool) (*OutgoingHtlcResolution, error) {
localCommit bool, chanType channeldb.ChannelType) (*OutgoingHtlcResolution, error) {
op := wire.OutPoint{
Hash: commitHash,
Index: uint32(htlc.OutputIndex),
}
// First, we'll re-generate the script used to send the HTLC to
// the remote party within their commitment transaction.
htlcScriptHash, htlcScript, err := genHtlcScript(
chanType, false, localCommit, htlc.RefundTimeout, htlc.RHash,
keyRing,
)
if err != nil {
return nil, err
}
// If we're spending this HTLC output from the remote node's
// commitment, then we won't need to go to the second level as our
// outputs don't have a CSV delay.
if !localCommit {
// First, we'll re-generate the script used to send the HTLC to
// the remote party within their commitment transaction.
htlcReceiverScript, err := input.ReceiverHTLCScript(htlc.RefundTimeout,
keyRing.LocalHtlcKey, keyRing.RemoteHtlcKey,
keyRing.RevocationKey, htlc.RHash[:],
)
if err != nil {
return nil, err
}
htlcScriptHash, err := input.WitnessScriptHash(htlcReceiverScript)
if err != nil {
return nil, err
}
// With the script generated, we can completely populated the
// SignDescriptor needed to sweep the output.
return &OutgoingHtlcResolution{
@ -5351,13 +5372,14 @@ func newOutgoingHtlcResolution(signer input.Signer,
SweepSignDesc: input.SignDescriptor{
KeyDesc: localChanCfg.HtlcBasePoint,
SingleTweak: keyRing.LocalHtlcKeyTweak,
WitnessScript: htlcReceiverScript,
WitnessScript: htlcScript,
Output: &wire.TxOut{
PkScript: htlcScriptHash,
Value: int64(htlc.Amt.ToSatoshis()),
},
HashType: txscript.SigHashAll,
},
CsvDelay: HtlcSecondLevelInputSequence(chanType),
}, nil
}
@ -5367,14 +5389,14 @@ func newOutgoingHtlcResolution(signer input.Signer,
// In order to properly reconstruct the HTLC transaction, we'll need to
// re-calculate the fee required at this state, so we can add the
// correct output value amount to the transaction.
htlcFee := htlcTimeoutFee(feePerKw)
htlcFee := HtlcTimeoutFee(chanType, feePerKw)
secondLevelOutputAmt := htlc.Amt.ToSatoshis() - htlcFee
// With the fee calculated, re-construct the second level timeout
// transaction.
timeoutTx, err := createHtlcTimeoutTx(
op, secondLevelOutputAmt, htlc.RefundTimeout, csvDelay,
keyRing.RevocationKey, keyRing.ToLocalKey,
chanType, op, secondLevelOutputAmt, htlc.RefundTimeout,
csvDelay, keyRing.RevocationKey, keyRing.ToLocalKey,
)
if err != nil {
return nil, err
@ -5383,15 +5405,10 @@ func newOutgoingHtlcResolution(signer input.Signer,
// With the transaction created, we can generate a sign descriptor
// that's capable of generating the signature required to spend the
// HTLC output using the timeout transaction.
htlcCreationScript, err := input.SenderHTLCScript(keyRing.LocalHtlcKey,
keyRing.RemoteHtlcKey, keyRing.RevocationKey, htlc.RHash[:])
if err != nil {
return nil, err
}
timeoutSignDesc := input.SignDescriptor{
KeyDesc: localChanCfg.HtlcBasePoint,
SingleTweak: keyRing.LocalHtlcKeyTweak,
WitnessScript: htlcCreationScript,
WitnessScript: htlcScript,
Output: &wire.TxOut{
Value: int64(htlc.Amt.ToSatoshis()),
},
@ -5402,8 +5419,9 @@ func newOutgoingHtlcResolution(signer input.Signer,
// With the sign desc created, we can now construct the full witness
// for the timeout transaction, and populate it as well.
sigHashType := HtlcSigHashType(chanType)
timeoutWitness, err := input.SenderHtlcSpendTimeout(
htlc.Signature, signer, &timeoutSignDesc, timeoutTx,
htlc.Signature, sigHashType, signer, &timeoutSignDesc, timeoutTx,
)
if err != nil {
return nil, err
@ -5419,7 +5437,7 @@ func newOutgoingHtlcResolution(signer input.Signer,
if err != nil {
return nil, err
}
htlcScriptHash, err := input.WitnessScriptHash(htlcSweepScript)
htlcSweepScriptHash, err := input.WitnessScriptHash(htlcSweepScript)
if err != nil {
return nil, err
}
@ -5440,7 +5458,7 @@ func newOutgoingHtlcResolution(signer input.Signer,
SingleTweak: localDelayTweak,
WitnessScript: htlcSweepScript,
Output: &wire.TxOut{
PkScript: htlcScriptHash,
PkScript: htlcSweepScriptHash,
Value: int64(secondLevelOutputAmt),
},
HashType: txscript.SigHashAll,
@ -5455,48 +5473,45 @@ func newOutgoingHtlcResolution(signer input.Signer,
// they can just sweep the output immediately with knowledge of the pre-image.
//
// TODO(roasbeef) consolidate code with above func
func newIncomingHtlcResolution(signer input.Signer, localChanCfg *channeldb.ChannelConfig,
commitHash chainhash.Hash, htlc *channeldb.HTLC, keyRing *CommitmentKeyRing,
feePerKw chainfee.SatPerKWeight, csvDelay uint32,
localCommit bool) (*IncomingHtlcResolution, error) {
func newIncomingHtlcResolution(signer input.Signer,
localChanCfg *channeldb.ChannelConfig, commitHash chainhash.Hash,
htlc *channeldb.HTLC, keyRing *CommitmentKeyRing,
feePerKw chainfee.SatPerKWeight, csvDelay uint32, localCommit bool,
chanType channeldb.ChannelType) (*IncomingHtlcResolution, error) {
op := wire.OutPoint{
Hash: commitHash,
Index: uint32(htlc.OutputIndex),
}
// First, we'll re-generate the script the remote party used to
// send the HTLC to us in their commitment transaction.
htlcScriptHash, htlcScript, err := genHtlcScript(
chanType, true, localCommit, htlc.RefundTimeout, htlc.RHash,
keyRing,
)
if err != nil {
return nil, err
}
// If we're spending this output from the remote node's commitment,
// then we can skip the second layer and spend the output directly.
if !localCommit {
// First, we'll re-generate the script the remote party used to
// send the HTLC to us in their commitment transaction.
htlcSenderScript, err := input.SenderHTLCScript(
keyRing.RemoteHtlcKey, keyRing.LocalHtlcKey,
keyRing.RevocationKey, htlc.RHash[:],
)
if err != nil {
return nil, err
}
htlcScriptHash, err := input.WitnessScriptHash(htlcSenderScript)
if err != nil {
return nil, err
}
// With the script generated, we can completely populated the
// SignDescriptor needed to sweep the output.
return &IncomingHtlcResolution{
ClaimOutpoint: op,
CsvDelay: csvDelay,
SweepSignDesc: input.SignDescriptor{
KeyDesc: localChanCfg.HtlcBasePoint,
SingleTweak: keyRing.LocalHtlcKeyTweak,
WitnessScript: htlcSenderScript,
WitnessScript: htlcScript,
Output: &wire.TxOut{
PkScript: htlcScriptHash,
Value: int64(htlc.Amt.ToSatoshis()),
},
HashType: txscript.SigHashAll,
},
CsvDelay: HtlcSecondLevelInputSequence(chanType),
}, nil
}
@ -5504,10 +5519,10 @@ func newIncomingHtlcResolution(signer input.Signer, localChanCfg *channeldb.Chan
// First, we'll reconstruct the original HTLC success transaction,
// taking into account the fee rate used.
htlcFee := htlcSuccessFee(feePerKw)
htlcFee := HtlcSuccessFee(chanType, feePerKw)
secondLevelOutputAmt := htlc.Amt.ToSatoshis() - htlcFee
successTx, err := createHtlcSuccessTx(
op, secondLevelOutputAmt, csvDelay,
chanType, op, secondLevelOutputAmt, csvDelay,
keyRing.RevocationKey, keyRing.ToLocalKey,
)
if err != nil {
@ -5516,17 +5531,10 @@ func newIncomingHtlcResolution(signer input.Signer, localChanCfg *channeldb.Chan
// Once we've created the second-level transaction, we'll generate the
// SignDesc needed spend the HTLC output using the success transaction.
htlcCreationScript, err := input.ReceiverHTLCScript(htlc.RefundTimeout,
keyRing.RemoteHtlcKey, keyRing.LocalHtlcKey,
keyRing.RevocationKey, htlc.RHash[:],
)
if err != nil {
return nil, err
}
successSignDesc := input.SignDescriptor{
KeyDesc: localChanCfg.HtlcBasePoint,
SingleTweak: keyRing.LocalHtlcKeyTweak,
WitnessScript: htlcCreationScript,
WitnessScript: htlcScript,
Output: &wire.TxOut{
Value: int64(htlc.Amt.ToSatoshis()),
},
@ -5539,8 +5547,10 @@ func newIncomingHtlcResolution(signer input.Signer, localChanCfg *channeldb.Chan
// the success transaction. Don't specify the preimage yet. The preimage
// will be supplied by the contract resolver, either directly or when it
// becomes known.
sigHashType := HtlcSigHashType(chanType)
successWitness, err := input.ReceiverHtlcSpendRedeem(
htlc.Signature, nil, signer, &successSignDesc, successTx,
htlc.Signature, sigHashType, nil, signer, &successSignDesc,
successTx,
)
if err != nil {
return nil, err
@ -5556,7 +5566,7 @@ func newIncomingHtlcResolution(signer input.Signer, localChanCfg *channeldb.Chan
if err != nil {
return nil, err
}
htlcScriptHash, err := input.WitnessScriptHash(htlcSweepScript)
htlcSweepScriptHash, err := input.WitnessScriptHash(htlcSweepScript)
if err != nil {
return nil, err
}
@ -5576,7 +5586,7 @@ func newIncomingHtlcResolution(signer input.Signer, localChanCfg *channeldb.Chan
SingleTweak: localDelayTweak,
WitnessScript: htlcSweepScript,
Output: &wire.TxOut{
PkScript: htlcScriptHash,
PkScript: htlcSweepScriptHash,
Value: int64(secondLevelOutputAmt),
},
HashType: txscript.SigHashAll,
@ -5614,7 +5624,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,
commitHash chainhash.Hash) (*HtlcResolutions, error) {
commitHash chainhash.Hash, chanType channeldb.ChannelType) (
*HtlcResolutions, error) {
// TODO(roasbeef): don't need to swap csv delay?
dustLimit := remoteChanCfg.DustLimit
@ -5627,11 +5638,15 @@ func extractHtlcResolutions(feePerKw chainfee.SatPerKWeight, ourCommit bool,
incomingResolutions := make([]IncomingHtlcResolution, 0, len(htlcs))
outgoingResolutions := make([]OutgoingHtlcResolution, 0, len(htlcs))
for _, htlc := range htlcs {
htlc := htlc
// We'll skip any HTLC's which were dust on the commitment
// transaction, as these don't have a corresponding output
// within the commitment transaction.
if htlcIsDust(htlc.Incoming, ourCommit, feePerKw,
htlc.Amt.ToSatoshis(), dustLimit) {
if htlcIsDust(
chanType, htlc.Incoming, ourCommit, feePerKw,
htlc.Amt.ToSatoshis(), dustLimit,
) {
continue
}
@ -5641,8 +5656,9 @@ func extractHtlcResolutions(feePerKw chainfee.SatPerKWeight, ourCommit bool,
// Otherwise, we'll create an incoming HTLC resolution
// as we can satisfy the contract.
ihr, err := newIncomingHtlcResolution(
signer, localChanCfg, commitHash, &htlc, keyRing,
feePerKw, uint32(csvDelay), ourCommit,
signer, localChanCfg, commitHash, &htlc,
keyRing, feePerKw, uint32(csvDelay), ourCommit,
chanType,
)
if err != nil {
return nil, err
@ -5654,7 +5670,7 @@ func extractHtlcResolutions(feePerKw chainfee.SatPerKWeight, ourCommit bool,
ohr, err := newOutgoingHtlcResolution(
signer, localChanCfg, commitHash, &htlc, keyRing,
feePerKw, uint32(csvDelay), ourCommit,
feePerKw, uint32(csvDelay), ourCommit, chanType,
)
if err != nil {
return nil, err
@ -5833,7 +5849,7 @@ func NewLocalForceCloseSummary(chanState *channeldb.OpenChannel, signer input.Si
htlcResolutions, err := extractHtlcResolutions(
chainfee.SatPerKWeight(localCommit.FeePerKw), true, signer,
localCommit.Htlcs, keyRing, &chanState.LocalChanCfg,
&chanState.RemoteChanCfg, txHash,
&chanState.RemoteChanCfg, txHash, chanState.ChanType,
)
if err != nil {
return nil, err
@ -6133,7 +6149,7 @@ func (lc *LightningChannel) availableCommitmentBalance(view *htlcView,
// For an extra HTLC fee to be paid on our commitment, the HTLC must be
// large enough to make a non-dust HTLC timeout transaction.
htlcFee := lnwire.NewMSatFromSatoshis(
htlcTimeoutFee(feePerKw),
HtlcTimeoutFee(lc.channelState.ChanType, feePerKw),
)
// If we are looking at the remote commitment, we must use the remote
@ -6143,7 +6159,7 @@ func (lc *LightningChannel) availableCommitmentBalance(view *htlcView,
lc.channelState.RemoteChanCfg.DustLimit,
)
htlcFee = lnwire.NewMSatFromSatoshis(
htlcSuccessFee(feePerKw),
HtlcSuccessFee(lc.channelState.ChanType, feePerKw),
)
}
@ -6351,7 +6367,7 @@ func CreateCooperativeCloseTx(fundingTxIn wire.TxIn,
// CalcFee returns the commitment fee to use for the given
// fee rate (fee-per-kw).
func (lc *LightningChannel) CalcFee(feeRate chainfee.SatPerKWeight) btcutil.Amount {
return feeRate.FeeForWeight(input.CommitWeight)
return feeRate.FeeForWeight(CommitWeight(lc.channelState.ChanType))
}
// MaxFeeRate returns the maximum fee rate given an allocation of the channel

@ -1011,7 +1011,8 @@ func TestHTLCDustLimit(t *testing.T) {
// The amount of the HTLC should be above Alice's dust limit and below
// Bob's dust limit.
htlcSat := (btcutil.Amount(500) + htlcTimeoutFee(
htlcSat := (btcutil.Amount(500) + HtlcTimeoutFee(
aliceChannel.channelState.ChanType,
chainfee.SatPerKWeight(
aliceChannel.channelState.LocalCommitment.FeePerKw,
),
@ -1119,8 +1120,12 @@ func TestHTLCSigNumber(t *testing.T) {
t.Fatalf("unable to get fee: %v", err)
}
belowDust := btcutil.Amount(500) + htlcTimeoutFee(feePerKw)
aboveDust := btcutil.Amount(1400) + htlcSuccessFee(feePerKw)
belowDust := btcutil.Amount(500) + HtlcTimeoutFee(
channeldb.SingleFunderTweaklessBit, feePerKw,
)
aboveDust := btcutil.Amount(1400) + HtlcSuccessFee(
channeldb.SingleFunderTweaklessBit, feePerKw,
)
// ===================================================================
// Test that Bob will reject a commitment if Alice doesn't send enough
@ -1278,7 +1283,8 @@ func TestChannelBalanceDustLimit(t *testing.T) {
defaultFee := calcStaticFee(1)
aliceBalance := aliceChannel.channelState.LocalCommitment.LocalBalance.ToSatoshis()
htlcSat := aliceBalance - defaultFee
htlcSat += htlcSuccessFee(
htlcSat += HtlcSuccessFee(
aliceChannel.channelState.ChanType,
chainfee.SatPerKWeight(
aliceChannel.channelState.LocalCommitment.FeePerKw,
),
@ -4759,10 +4765,10 @@ func TestChanAvailableBalanceNearHtlcFee(t *testing.T) {
aliceChannel.channelState.LocalCommitment.CommitFee,
)
htlcTimeoutFee := lnwire.NewMSatFromSatoshis(
htlcTimeoutFee(feeRate),
HtlcTimeoutFee(aliceChannel.channelState.ChanType, feeRate),
)
htlcSuccessFee := lnwire.NewMSatFromSatoshis(
htlcSuccessFee(feeRate),
HtlcSuccessFee(aliceChannel.channelState.ChanType, feeRate),
)
// Helper method to check the current reported balance.
@ -6273,7 +6279,8 @@ func TestChanReserveLocalInitiatorDustHtlc(t *testing.T) {
// limit (1300 sat). It is considered dust if the amount remaining
// after paying the HTLC fee is below the dustlimit, so we choose a
// size of 500+htlcFee.
htlcSat := btcutil.Amount(500) + htlcTimeoutFee(
htlcSat := btcutil.Amount(500) + HtlcTimeoutFee(
aliceChannel.channelState.ChanType,
chainfee.SatPerKWeight(
aliceChannel.channelState.LocalCommitment.FeePerKw,
),

@ -5,6 +5,7 @@ import (
"github.com/btcsuite/btcd/blockchain"
"github.com/btcsuite/btcd/btcec"
"github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btcutil"
"github.com/lightningnetwork/lnd/channeldb"
@ -13,6 +14,9 @@ import (
"github.com/lightningnetwork/lnd/lnwire"
)
// anchorSize is the constant anchor output size.
const anchorSize = btcutil.Amount(330)
// 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
@ -183,13 +187,34 @@ type ScriptInfo struct {
// CommitScriptToRemote creates the script that will pay to the non-owner of
// the commitment transaction, adding a delay to the script based on the
// channel type.
func CommitScriptToRemote(_ channeldb.ChannelType, csvTimeout uint32,
key *btcec.PublicKey) (*ScriptInfo, error) {
// channel type. The second return value is the CSV deleay 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) {
// If this channel type has anchors, we derive the delayed to_remote
// script.
if chanType.HasAnchors() {
script, err := input.CommitScriptToRemoteConfirmed(key)
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
}
// Otherwise the to_remote will be a simple p2wkh.
p2wkh, err := input.CommitScriptUnencumbered(key)
if err != nil {
return nil, err
return nil, 0, err
}
// Since this is a regular P2WKH, the WitnessScipt and PkScript should
@ -197,7 +222,100 @@ func CommitScriptToRemote(_ channeldb.ChannelType, csvTimeout uint32,
return &ScriptInfo{
WitnessScript: p2wkh,
PkScript: p2wkh,
}, nil
}, 0, nil
}
// HtlcSigHashType returns the sighash type to use for HTLC success and timeout
// transactions given the channel type.
func HtlcSigHashType(chanType channeldb.ChannelType) txscript.SigHashType {
if chanType.HasAnchors() {
return txscript.SigHashSingle | txscript.SigHashAnyOneCanPay
}
return txscript.SigHashAll
}
// HtlcSecondLevelInputSequence dictates the sequence number we must use on the
// input to a second level HTLC transaction.
func HtlcSecondLevelInputSequence(chanType channeldb.ChannelType) uint32 {
if chanType.HasAnchors() {
return 1
}
return 0
}
// CommitWeight returns the base commitment weight before adding HTLCs.
func CommitWeight(chanType channeldb.ChannelType) int64 {
// If this commitment has anchors, it will be slightly heavier.
if chanType.HasAnchors() {
return input.AnchorCommitWeight
}
return input.CommitWeight
}
// HtlcTimeoutFee returns the fee in satoshis required for an HTLC timeout
// transaction based on the current fee rate.
func HtlcTimeoutFee(chanType channeldb.ChannelType,
feePerKw chainfee.SatPerKWeight) btcutil.Amount {
if chanType.HasAnchors() {
return feePerKw.FeeForWeight(input.HtlcTimeoutWeightConfirmed)
}
return feePerKw.FeeForWeight(input.HtlcTimeoutWeight)
}
// HtlcSuccessFee returns the fee in satoshis required for an HTLC success
// transaction based on the current fee rate.
func HtlcSuccessFee(chanType channeldb.ChannelType,
feePerKw chainfee.SatPerKWeight) btcutil.Amount {
if chanType.HasAnchors() {
return feePerKw.FeeForWeight(input.HtlcSuccessWeightConfirmed)
}
return feePerKw.FeeForWeight(input.HtlcSuccessWeight)
}
// CommitScriptAnchors return the scripts to use for the local and remote
// anchor.
func CommitScriptAnchors(localChanCfg,
remoteChanCfg *channeldb.ChannelConfig) (*ScriptInfo,
*ScriptInfo, error) {
// Helper to create anchor ScriptInfo from key.
anchorScript := func(key *btcec.PublicKey) (*ScriptInfo, error) {
script, err := input.CommitScriptAnchor(key)
if err != nil {
return nil, err
}
scriptHash, err := input.WitnessScriptHash(script)
if err != nil {
return nil, err
}
return &ScriptInfo{
PkScript: scriptHash,
WitnessScript: script,
}, nil
}
// Get the script used for the anchor output spendable by the local
// node.
localAnchor, err := anchorScript(localChanCfg.MultiSigKey.PubKey)
if err != nil {
return nil, nil, err
}
// And the anchor spemdable by the remote node.
remoteAnchor, err := anchorScript(remoteChanCfg.MultiSigKey.PubKey)
if err != nil {
return nil, nil, err
}
return localAnchor, remoteAnchor, nil
}
// CommitmentBuilder is a type that wraps the type of channel we are dealing
@ -216,6 +334,11 @@ type CommitmentBuilder struct {
// NewCommitmentBuilder creates a new CommitmentBuilder from chanState.
func NewCommitmentBuilder(chanState *channeldb.OpenChannel) *CommitmentBuilder {
// The anchor channel type MUST be tweakless.
if chanState.ChanType.HasAnchors() && !chanState.ChanType.IsTweakless() {
panic("invalid channel type combination")
}
return &CommitmentBuilder{
chanState: chanState,
obfuscator: createStateHintObfuscator(chanState),
@ -248,8 +371,9 @@ type unsignedCommitmentTx struct {
// fee is the total fee of the commitment transaction.
fee btcutil.Amount
// ourBalance|theirBalance is the balances of this commitment. This can
// be different than the balances before creating the commitment
// ourBalance|theirBalance are the balances of this commitment *after*
// subtracting commitment fees and anchor outputs. This can be
// different than the balances before creating the commitment
// transaction as one party must pay the commitment fee.
ourBalance lnwire.MilliSatoshi
theirBalance lnwire.MilliSatoshi
@ -258,7 +382,7 @@ type unsignedCommitmentTx struct {
// createUnsignedCommitmentTx generates the unsigned commitment transaction for
// a commitment view and returns it as part of the unsignedCommitmentTx. The
// passed in balances should be balances *before* subtracting any commitment
// fees.
// fees, but after anchor outputs.
func (cb *CommitmentBuilder) createUnsignedCommitmentTx(ourBalance,
theirBalance lnwire.MilliSatoshi, isOurs bool,
feePerKw chainfee.SatPerKWeight, height uint64,
@ -272,18 +396,20 @@ func (cb *CommitmentBuilder) createUnsignedCommitmentTx(ourBalance,
numHTLCs := int64(0)
for _, htlc := range filteredHTLCView.ourUpdates {
if htlcIsDust(false, isOurs, feePerKw,
htlc.Amount.ToSatoshis(), dustLimit) {
if htlcIsDust(
cb.chanState.ChanType, false, isOurs, feePerKw,
htlc.Amount.ToSatoshis(), dustLimit,
) {
continue
}
numHTLCs++
}
for _, htlc := range filteredHTLCView.theirUpdates {
if htlcIsDust(true, isOurs, feePerKw,
htlc.Amount.ToSatoshis(), dustLimit) {
if htlcIsDust(
cb.chanState.ChanType, true, isOurs, feePerKw,
htlc.Amount.ToSatoshis(), dustLimit,
) {
continue
}
@ -294,7 +420,8 @@ func (cb *CommitmentBuilder) createUnsignedCommitmentTx(ourBalance,
// on its total weight. Once we have the total weight, we'll multiply
// by the current fee-per-kw, then divide by 1000 to get the proper
// fee.
totalCommitWeight := input.CommitWeight + (input.HTLCWeight * numHTLCs)
totalCommitWeight := CommitWeight(cb.chanState.ChanType) +
input.HTLCWeight*numHTLCs
// With the weight known, we can now calculate the commitment fee,
// ensuring that we account for any dust outputs trimmed above.
@ -333,12 +460,14 @@ func (cb *CommitmentBuilder) createUnsignedCommitmentTx(ourBalance,
cb.chanState.ChanType, fundingTxIn(cb.chanState), keyRing,
&cb.chanState.LocalChanCfg, &cb.chanState.RemoteChanCfg,
ourBalance.ToSatoshis(), theirBalance.ToSatoshis(),
numHTLCs,
)
} else {
commitTx, err = CreateCommitTx(
cb.chanState.ChanType, fundingTxIn(cb.chanState), keyRing,
&cb.chanState.RemoteChanCfg, &cb.chanState.LocalChanCfg,
theirBalance.ToSatoshis(), ourBalance.ToSatoshis(),
numHTLCs,
)
}
if err != nil {
@ -356,24 +485,34 @@ func (cb *CommitmentBuilder) createUnsignedCommitmentTx(ourBalance,
// purposes of sorting.
cltvs := make([]uint32, len(commitTx.TxOut))
for _, htlc := range filteredHTLCView.ourUpdates {
if htlcIsDust(false, isOurs, feePerKw,
htlc.Amount.ToSatoshis(), dustLimit) {
if htlcIsDust(
cb.chanState.ChanType, false, isOurs, feePerKw,
htlc.Amount.ToSatoshis(), dustLimit,
) {
continue
}
err := addHTLC(commitTx, isOurs, false, htlc, keyRing)
err := addHTLC(
commitTx, isOurs, false, htlc, keyRing,
cb.chanState.ChanType,
)
if err != nil {
return nil, err
}
cltvs = append(cltvs, htlc.Timeout)
}
for _, htlc := range filteredHTLCView.theirUpdates {
if htlcIsDust(true, isOurs, feePerKw,
htlc.Amount.ToSatoshis(), dustLimit) {
if htlcIsDust(
cb.chanState.ChanType, true, isOurs, feePerKw,
htlc.Amount.ToSatoshis(), dustLimit,
) {
continue
}
err := addHTLC(commitTx, isOurs, true, htlc, keyRing)
err := addHTLC(
commitTx, isOurs, true, htlc, keyRing,
cb.chanState.ChanType,
)
if err != nil {
return nil, err
}
@ -430,7 +569,8 @@ func (cb *CommitmentBuilder) createUnsignedCommitmentTx(ourBalance,
func CreateCommitTx(chanType channeldb.ChannelType,
fundingOutput wire.TxIn, keyRing *CommitmentKeyRing,
localChanCfg, remoteChanCfg *channeldb.ChannelConfig,
amountToLocal, amountToRemote btcutil.Amount) (*wire.MsgTx, error) {
amountToLocal, amountToRemote btcutil.Amount,
numHTLCs int64) (*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
@ -452,8 +592,8 @@ func CreateCommitTx(chanType channeldb.ChannelType,
}
// Next, we create the script paying to the remote.
toRemoteScript, err := CommitScriptToRemote(
chanType, uint32(remoteChanCfg.CsvDelay), keyRing.ToRemoteKey,
toRemoteScript, _, err := CommitScriptToRemote(
chanType, keyRing.ToRemoteKey,
)
if err != nil {
return nil, err
@ -466,26 +606,58 @@ func CreateCommitTx(chanType channeldb.ChannelType,
commitTx.AddTxIn(&fundingOutput)
// Avoid creating dust outputs within the commitment transaction.
if amountToLocal >= localChanCfg.DustLimit {
localOutput := amountToLocal >= localChanCfg.DustLimit
if localOutput {
commitTx.AddTxOut(&wire.TxOut{
PkScript: toLocalScriptHash,
Value: int64(amountToLocal),
})
}
if amountToRemote >= localChanCfg.DustLimit {
remoteOutput := amountToRemote >= localChanCfg.DustLimit
if remoteOutput {
commitTx.AddTxOut(&wire.TxOut{
PkScript: toRemoteScript.PkScript,
Value: int64(amountToRemote),
})
}
// If this channel type has anchors, we'll also add those.
if chanType.HasAnchors() {
localAnchor, remoteAnchor, err := CommitScriptAnchors(
localChanCfg, remoteChanCfg,
)
if err != nil {
return nil, err
}
// Add local anchor output only if we have a commitment output
// or there are HTLCs.
if localOutput || numHTLCs > 0 {
commitTx.AddTxOut(&wire.TxOut{
PkScript: localAnchor.PkScript,
Value: int64(anchorSize),
})
}
// Add anchor output to remote only if they have a commitment
// output or there are HTLCs.
if remoteOutput || numHTLCs > 0 {
commitTx.AddTxOut(&wire.TxOut{
PkScript: remoteAnchor.PkScript,
Value: int64(anchorSize),
})
}
}
return commitTx, nil
}
// genHtlcScript generates the proper P2WSH public key scripts for the HTLC
// output modified by two-bits denoting if this is an incoming HTLC, and if the
// HTLC is being applied to their commitment transaction or ours.
func genHtlcScript(isIncoming, ourCommit bool, timeout uint32, rHash [32]byte,
func genHtlcScript(chanType channeldb.ChannelType, isIncoming, ourCommit bool,
timeout uint32, rHash [32]byte,
keyRing *CommitmentKeyRing) ([]byte, []byte, error) {
var (
@ -493,6 +665,12 @@ func genHtlcScript(isIncoming, ourCommit bool, timeout uint32, rHash [32]byte,
err error
)
// Choose scripts based on channel type.
confirmedHtlcSpends := false
if chanType.HasAnchors() {
confirmedHtlcSpends = true
}
// Generate the proper redeem scripts for the HTLC output modified by
// two-bits denoting if this is an incoming HTLC, and if the HTLC is
// being applied to their commitment transaction or ours.
@ -501,30 +679,37 @@ func genHtlcScript(isIncoming, ourCommit bool, timeout uint32, rHash [32]byte,
// transaction. So we need to use the receiver's version of HTLC the
// script.
case isIncoming && ourCommit:
witnessScript, err = input.ReceiverHTLCScript(timeout,
keyRing.RemoteHtlcKey, keyRing.LocalHtlcKey,
keyRing.RevocationKey, rHash[:])
witnessScript, err = input.ReceiverHTLCScript(
timeout, keyRing.RemoteHtlcKey, keyRing.LocalHtlcKey,
keyRing.RevocationKey, rHash[:], confirmedHtlcSpends,
)
// 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[:])
witnessScript, err = input.SenderHTLCScript(
keyRing.RemoteHtlcKey, keyRing.LocalHtlcKey,
keyRing.RevocationKey, rHash[:], confirmedHtlcSpends,
)
// 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[:])
witnessScript, err = input.SenderHTLCScript(
keyRing.LocalHtlcKey, keyRing.RemoteHtlcKey,
keyRing.RevocationKey, rHash[:], confirmedHtlcSpends,
)
// 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[:])
witnessScript, err = input.ReceiverHTLCScript(
timeout, keyRing.LocalHtlcKey, keyRing.RemoteHtlcKey,
keyRing.RevocationKey, rHash[:], confirmedHtlcSpends,
)
}
if err != nil {
return nil, nil, err
@ -549,13 +734,14 @@ func genHtlcScript(isIncoming, ourCommit bool, timeout uint32, rHash [32]byte,
// the descriptor itself.
func addHTLC(commitTx *wire.MsgTx, ourCommit bool,
isIncoming bool, paymentDesc *PaymentDescriptor,
keyRing *CommitmentKeyRing) error {
keyRing *CommitmentKeyRing, chanType channeldb.ChannelType) error {
timeout := paymentDesc.Timeout
rHash := paymentDesc.RHash
p2wsh, witnessScript, err := genHtlcScript(isIncoming, ourCommit,
timeout, rHash, keyRing)
p2wsh, witnessScript, err := genHtlcScript(
chanType, isIncoming, ourCommit, timeout, rHash, keyRing,
)
if err != nil {
return err
}

@ -700,7 +700,8 @@ func testCancelNonExistentReservation(miner *rpctest.Harness,
// Create our own reservation, give it some ID.
res, err := lnwallet.NewChannelReservation(
10000, 10000, feePerKw, alice, 22, 10, &testHdSeed,
lnwire.FFAnnounceChannel, true, nil, [32]byte{},
lnwire.FFAnnounceChannel, lnwallet.CommitmentTypeTweakless,
nil, [32]byte{},
)
if err != nil {
t.Fatalf("unable to create res: %v", err)
@ -738,7 +739,7 @@ func testReservationInitiatorBalanceBelowDustCancel(miner *rpctest.Harness,
FundingFeePerKw: 1000,
PushMSat: 0,
Flags: lnwire.FFAnnounceChannel,
Tweakless: true,
CommitType: lnwallet.CommitmentTypeTweakless,
}
_, err = alice.InitChannelReservation(req)
switch {
@ -793,7 +794,8 @@ func assertContributionInitPopulated(t *testing.T, c *lnwallet.ChannelContributi
}
func testSingleFunderReservationWorkflow(miner *rpctest.Harness,
alice, bob *lnwallet.LightningWallet, t *testing.T, tweakless bool,
alice, bob *lnwallet.LightningWallet, t *testing.T,
commitType lnwallet.CommitmentType,
aliceChanFunder chanfunding.Assembler,
fetchFundingTx func() *wire.MsgTx, pendingChanID [32]byte) {
@ -823,7 +825,7 @@ func testSingleFunderReservationWorkflow(miner *rpctest.Harness,
FundingFeePerKw: feePerKw,
PushMSat: pushAmt,
Flags: lnwire.FFAnnounceChannel,
Tweakless: tweakless,
CommitType: commitType,
ChanFunder: aliceChanFunder,
}
aliceChanReservation, err := alice.InitChannelReservation(aliceReq)
@ -874,7 +876,7 @@ func testSingleFunderReservationWorkflow(miner *rpctest.Harness,
FundingFeePerKw: feePerKw,
PushMSat: pushAmt,
Flags: lnwire.FFAnnounceChannel,
Tweakless: tweakless,
CommitType: commitType,
}
bobChanReservation, err := bob.InitChannelReservation(bobReq)
if err != nil {
@ -2543,7 +2545,8 @@ var walletTests = []walletTestCase{
bob *lnwallet.LightningWallet, t *testing.T) {
testSingleFunderReservationWorkflow(
miner, alice, bob, t, false, nil, nil,
miner, alice, bob, t,
lnwallet.CommitmentTypeLegacy, nil, nil,
[32]byte{},
)
},
@ -2554,7 +2557,8 @@ var walletTests = []walletTestCase{
bob *lnwallet.LightningWallet, t *testing.T) {
testSingleFunderReservationWorkflow(
miner, alice, bob, t, true, nil, nil,
miner, alice, bob, t,
lnwallet.CommitmentTypeTweakless, nil, nil,
[32]byte{},
)
},
@ -2809,8 +2813,8 @@ func testSingleFunderExternalFundingTx(miner *rpctest.Harness,
// pending channel ID generated above to allow Alice and Bob to track
// the funding flow externally.
testSingleFunderReservationWorkflow(
miner, alice, bob, t, true, aliceExternalFunder,
func() *wire.MsgTx {
miner, alice, bob, t, lnwallet.CommitmentTypeTweakless,
aliceExternalFunder, func() *wire.MsgTx {
return fundingTx
}, pendingChanID,
)

@ -15,6 +15,39 @@ import (
"github.com/lightningnetwork/lnd/lnwire"
)
// CommitmentType is an enum indicating the commitment type we should use for
// the channel we are opening.
type CommitmentType int
const (
// CommitmentTypeLegacy is the legacy commitment format with a tweaked
// to_remote key.
CommitmentTypeLegacy = iota
// CommitmentTypeTweakless is a newer commitment format where the
// to_remote key is static.
CommitmentTypeTweakless
// CommitmentTypeAnchors is a commitment type that is tweakless, and
// has extra anchor ouputs in order to bump the fee of the commitment
// transaction.
CommitmentTypeAnchors
)
// String returns the name of the CommitmentType.
func (c CommitmentType) String() string {
switch c {
case CommitmentTypeLegacy:
return "legacy"
case CommitmentTypeTweakless:
return "tweakless"
case CommitmentTypeAnchors:
return "anchors"
default:
return "invalid"
}
}
// ChannelContribution is the primary constituent of the funding workflow
// within lnwallet. Each side first exchanges their respective contributions
// along with channel specific parameters like the min fee/KB. Once
@ -136,7 +169,7 @@ type ChannelReservation struct {
func NewChannelReservation(capacity, localFundingAmt btcutil.Amount,
commitFeePerKw chainfee.SatPerKWeight, wallet *LightningWallet,
id uint64, pushMSat lnwire.MilliSatoshi, chainHash *chainhash.Hash,
flags lnwire.FundingFlag, tweaklessCommit bool,
flags lnwire.FundingFlag, commitType CommitmentType,
fundingAssembler chanfunding.Assembler,
pendingChanID [32]byte) (*ChannelReservation, error) {
@ -146,12 +179,25 @@ func NewChannelReservation(capacity, localFundingAmt btcutil.Amount,
initiator bool
)
commitFee := commitFeePerKw.FeeForWeight(input.CommitWeight)
// Based on the channel type, we determine the initial commit weight
// and fee.
commitWeight := int64(input.CommitWeight)
if commitType == CommitmentTypeAnchors {
commitWeight = input.AnchorCommitWeight
}
commitFee := commitFeePerKw.FeeForWeight(commitWeight)
localFundingMSat := lnwire.NewMSatFromSatoshis(localFundingAmt)
// TODO(halseth): make method take remote funding amount directly
// instead of inferring it from capacity and local amt.
capacityMSat := lnwire.NewMSatFromSatoshis(capacity)
// The total fee paid by the initiator will be the commitment fee in
// addition to the two anchor outputs.
feeMSat := lnwire.NewMSatFromSatoshis(commitFee)
if commitType == CommitmentTypeAnchors {
feeMSat += 2 * lnwire.NewMSatFromSatoshis(anchorSize)
}
// If we're the responder to a single-funder reservation, then we have
// no initial balance in the channel unless the remote party is pushing
@ -213,6 +259,16 @@ func NewChannelReservation(capacity, localFundingAmt btcutil.Amount,
)
}
// Similarly we ensure their balance is reasonable if we are not the
// initiator.
if !initiator && theirBalance.ToSatoshis() <= 2*DefaultDustLimit() {
return nil, ErrFunderBalanceDust(
int64(commitFee),
int64(theirBalance.ToSatoshis()),
int64(2*DefaultDustLimit()),
)
}
// Next we'll set the channel type based on what we can ascertain about
// the balances/push amount within the channel.
var chanType channeldb.ChannelType
@ -221,7 +277,11 @@ func NewChannelReservation(capacity, localFundingAmt btcutil.Amount,
// non-zero push amt (there's no pushing for dual funder), then this is
// a single-funder channel.
if ourBalance == 0 || theirBalance == 0 || pushMSat != 0 {
if tweaklessCommit {
// Both the tweakless type and the anchor type is tweakless,
// hence set the bit.
if commitType == CommitmentTypeTweakless ||
commitType == CommitmentTypeAnchors {
chanType |= channeldb.SingleFunderTweaklessBit
} else {
chanType |= channeldb.SingleFunderBit
@ -241,6 +301,11 @@ func NewChannelReservation(capacity, localFundingAmt btcutil.Amount,
chanType |= channeldb.DualFunderBit
}
// We are adding anchor outputs to our commitment.
if commitType == CommitmentTypeAnchors {
chanType |= channeldb.AnchorOutputsBit
}
return &ChannelReservation{
ourContribution: &ChannelContribution{
FundingAmount: ourBalance.ToSatoshis(),

@ -7,6 +7,7 @@ import (
"github.com/btcsuite/btcd/btcec"
"github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btcutil"
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/input"
)
@ -44,8 +45,8 @@ var (
// In order to spend the HTLC output, the witness for the passed transaction
// should be:
// * <0> <sender sig> <recvr sig> <preimage>
func createHtlcSuccessTx(htlcOutput wire.OutPoint, htlcAmt btcutil.Amount,
csvDelay uint32,
func createHtlcSuccessTx(chanType channeldb.ChannelType,
htlcOutput wire.OutPoint, htlcAmt btcutil.Amount, csvDelay uint32,
revocationKey, delayKey *btcec.PublicKey) (*wire.MsgTx, error) {
// Create a version two transaction (as the success version of this
@ -53,10 +54,13 @@ func createHtlcSuccessTx(htlcOutput wire.OutPoint, htlcAmt btcutil.Amount,
successTx := wire.NewMsgTx(2)
// The input to the transaction is the outpoint that creates the
// original HTLC on the sender's commitment transaction.
successTx.AddTxIn(&wire.TxIn{
// original HTLC on the sender's commitment transaction. Set the
// sequence number based on the channel type.
txin := &wire.TxIn{
PreviousOutPoint: htlcOutput,
})
Sequence: HtlcSecondLevelInputSequence(chanType),
}
successTx.AddTxIn(txin)
// Next, we'll generate the script used as the output for all second
// level HTLC which forces a covenant w.r.t what can be done with all
@ -97,7 +101,8 @@ func createHtlcSuccessTx(htlcOutput wire.OutPoint, htlcAmt btcutil.Amount,
// 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(htlcOutput wire.OutPoint, htlcAmt btcutil.Amount,
func createHtlcTimeoutTx(chanType channeldb.ChannelType,
htlcOutput wire.OutPoint, htlcAmt btcutil.Amount,
cltvExpiry, csvDelay uint32,
revocationKey, delayKey *btcec.PublicKey) (*wire.MsgTx, error) {
@ -108,10 +113,13 @@ func createHtlcTimeoutTx(htlcOutput wire.OutPoint, htlcAmt btcutil.Amount,
timeoutTx.LockTime = cltvExpiry
// The input to the transaction is the outpoint that creates the
// original HTLC on the sender's commitment transaction.
timeoutTx.AddTxIn(&wire.TxIn{
// original HTLC on the sender's commitment transaction. Set the
// sequence number based on the channel type.
txin := &wire.TxIn{
PreviousOutPoint: htlcOutput,
})
Sequence: HtlcSecondLevelInputSequence(chanType),
}
timeoutTx.AddTxIn(txin)
// Next, we'll generate the script used as the output for all second
// level HTLC which forces a covenant w.r.t what can be done with all

@ -851,9 +851,10 @@ func TestCommitmentAndHTLCTransactions(t *testing.T) {
// Generate second-level HTLC transactions for HTLCs in
// commitment tx.
htlcResolutions, err := extractHtlcResolutions(
chainfee.SatPerKWeight(test.commitment.FeePerKw), true, signer,
htlcs, keys, &channel.channelState.LocalChanCfg,
chainfee.SatPerKWeight(test.commitment.FeePerKw), true,
signer, htlcs, keys, &channel.channelState.LocalChanCfg,
&channel.channelState.RemoteChanCfg, commitTx.TxHash(),
channel.channelState.ChanType,
)
if err != nil {
t.Errorf("Case %d: Failed to extract HTLC resolutions: %v", i, err)
@ -1089,7 +1090,7 @@ func testSpendValidation(t *testing.T, tweakless bool) {
}
commitmentTx, err := CreateCommitTx(
channelType, *fakeFundingTxIn, keyRing, aliceChanCfg,
bobChanCfg, channelBalance, channelBalance,
bobChanCfg, channelBalance, channelBalance, 0,
)
if err != nil {
t.Fatalf("unable to create commitment transaction: %v", nil)

@ -98,9 +98,9 @@ type InitFundingReserveMsg struct {
// output selected to fund the channel should satisfy.
MinConfs int32
// Tweakless indicates if the channel should use the new tweakless
// commitment format or not.
Tweakless bool
// CommitType indicates what type of commitment type the channel should
// be using, like tweakless or anchors.
CommitType CommitmentType
// ChanFunder is an optional channel funder that allows the caller to
// control exactly how the channel funding is carried out. If not
@ -568,7 +568,7 @@ func (l *LightningWallet) handleFundingReserveRequest(req *InitFundingReserveMsg
reservation, err := NewChannelReservation(
capacity, localFundingAmt, req.CommitFeePerKw, l, id,
req.PushMSat, l.Cfg.NetParams.GenesisHash, req.Flags,
req.Tweakless, req.ChanFunder, req.PendingChanID,
req.CommitType, req.ChanFunder, req.PendingChanID,
)
if err != nil {
if fundingIntent != nil {
@ -784,7 +784,7 @@ func CreateCommitmentTxns(localBalance, remoteBalance btcutil.Amount,
ourCommitTx, err := CreateCommitTx(
chanType, fundingTxIn, localCommitmentKeys, ourChanCfg,
theirChanCfg, localBalance, remoteBalance,
theirChanCfg, localBalance, remoteBalance, 0,
)
if err != nil {
return nil, nil, err
@ -797,7 +797,7 @@ func CreateCommitmentTxns(localBalance, remoteBalance btcutil.Amount,
theirCommitTx, err := CreateCommitTx(
chanType, fundingTxIn, remoteCommitmentKeys, theirChanCfg,
ourChanCfg, remoteBalance, localBalance,
ourChanCfg, remoteBalance, localBalance, 0,
)
if err != nil {
return nil, nil, err

@ -101,6 +101,16 @@ const (
// HTLC.
MPPOptional FeatureBit = 17
// AnchorsRequired is a required feature bit that signals that the node
// requires channels to be made using commitments having anchor
// outputs.
AnchorsRequired FeatureBit = 1336
// AnchorsRequired is an optional feature bit that signals that the
// node supports channels to be made using commitments having anchor
// outputs.
AnchorsOptional FeatureBit = 1337
// maxAllowedSize is a maximum allowed size of feature vector.
//
// NOTE: Within the protocol, the maximum allowed message size is 65535
@ -138,6 +148,8 @@ var Features = map[FeatureBit]string{
PaymentAddrRequired: "payment-addr",
MPPOptional: "multi-path-payments",
MPPRequired: "multi-path-payments",
AnchorsRequired: "anchor-commitments",
AnchorsOptional: "anchor-commitments",
}
// RawFeatureVector represents a set of feature bits as defined in BOLT-09. A

@ -336,16 +336,23 @@ func newServer(listenAddrs []net.Addr, chanDB *channeldb.DB,
// Only if we're not being forced to use the legacy onion format, will
// we signal our knowledge of the new TLV onion format.
if !cfg.LegacyProtocol.LegacyOnion() {
if !cfg.ProtocolOptions.LegacyOnion() {
globalFeatures.Set(lnwire.TLVOnionPayloadOptional)
}
// Similarly, we default to the new modern commitment format unless the
// legacy commitment config is set to true.
if !cfg.LegacyProtocol.LegacyCommitment() {
// Similarly, we default to supporting the new modern commitment format
// where the remote key is static unless the protocol config is set to
// keep using the older format.
if !cfg.ProtocolOptions.NoStaticRemoteKey() {
globalFeatures.Set(lnwire.StaticRemoteKeyOptional)
}
// We only signal that we support the experimental anchor commitments
// if explicitly enabled in the config.
if cfg.ProtocolOptions.AnchorCommitments() {
globalFeatures.Set(lnwire.AnchorsOptional)
}
var serializedPubKey [33]byte
copy(serializedPubKey[:], privKey.PubKey().SerializeCompressed())
@ -375,8 +382,9 @@ func newServer(listenAddrs []net.Addr, chanDB *channeldb.DB,
)
featureMgr, err := feature.NewManager(feature.Config{
NoTLVOnion: cfg.LegacyProtocol.LegacyOnion(),
NoStaticRemoteKey: cfg.LegacyProtocol.LegacyCommitment(),
NoTLVOnion: cfg.ProtocolOptions.LegacyOnion(),
NoStaticRemoteKey: cfg.ProtocolOptions.NoStaticRemoteKey(),
NoAnchors: !cfg.ProtocolOptions.AnchorCommitments(),
})
if err != nil {
return nil, err

@ -14,7 +14,7 @@ var (
input.HtlcOfferedRemoteTimeout,
input.WitnessKeyHash,
}
expectedWeight = int64(1459)
expectedWeight = int64(1462)
expectedSummary = "1 CommitmentTimeLock, 1 " +
"HtlcAcceptedSuccessSecondLevel, 1 HtlcOfferedRemoteTimeout, " +
"1 WitnessKeyHash"

@ -397,10 +397,11 @@ func (u *utxoNursery) IncubateOutputs(chanPoint wire.OutPoint,
// Otherwise, this is actually a kid output as we can sweep it
// once the commitment transaction confirms, and the absolute
// CLTV lock has expired. We set the CSV delay to zero to
// indicate this is actually a CLTV output.
// CLTV lock has expired. We set the CSV delay what the
// resolution encodes, since the sequence number must be set
// accordingly.
htlcOutput := makeKidOutput(
&htlcRes.ClaimOutpoint, &chanPoint, 0,
&htlcRes.ClaimOutpoint, &chanPoint, htlcRes.CsvDelay,
input.HtlcOfferedRemoteTimeout,
&htlcRes.SweepSignDesc, htlcRes.Expiry,
)
@ -1271,7 +1272,8 @@ type kidOutput struct {
// output.
//
// NOTE: This will be set for: commitment outputs, and incoming HTLC's.
// Otherwise, this will be zero.
// Otherwise, this will be zero. It will also be non-zero for
// commitment types which requires confirmed spends.
blocksToMaturity uint32
// absoluteMaturity is the absolute height that this output will be

@ -603,7 +603,6 @@ func createOutgoingRes(onLocalCommitment bool) *lnwallet.OutgoingHtlcResolution
Value: 10000,
},
},
CsvDelay: 2,
}
if onLocalCommitment {
@ -620,8 +619,10 @@ func createOutgoingRes(onLocalCommitment bool) *lnwallet.OutgoingHtlcResolution
}
outgoingRes.SignedTimeoutTx = timeoutTx
outgoingRes.CsvDelay = 2
} else {
outgoingRes.ClaimOutpoint = htlcOp
outgoingRes.CsvDelay = 0
}
return &outgoingRes