Merge pull request #3821 from halseth/pluggable-anchors-lnwallet
[anchor] pluggable anchor commitments
This commit is contained in:
commit
3dda93e30d
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
109
input/size.go
109
input/size.go
@ -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
|
||||
|
20
server.go
20
server.go
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user