Skip to content

Commit

Permalink
Merge pull request #7733 from ellemouton/taprootTowers
Browse files Browse the repository at this point in the history
watchtower: support taproot channel commitments
  • Loading branch information
ellemouton authored Jan 19, 2024
2 parents 24a79c7 + 2a61c91 commit 0a29b37
Show file tree
Hide file tree
Showing 27 changed files with 1,451 additions and 319 deletions.
12 changes: 10 additions & 2 deletions cmd/lncli/wtclient.go
Original file line number Diff line number Diff line change
Expand Up @@ -284,8 +284,14 @@ var policyCommand = cli.Command{
"policy. (default)",
},
cli.BoolFlag{
Name: "anchor",
Usage: "Retrieve the anchor tower client's current policy.",
Name: "anchor",
Usage: "Retrieve the anchor tower client's current " +
"policy.",
},
cli.BoolFlag{
Name: "taproot",
Usage: "Retrieve the taproot tower client's current " +
"policy.",
},
},
}
Expand All @@ -305,6 +311,8 @@ func policy(ctx *cli.Context) error {
policyType = wtclientrpc.PolicyType_ANCHOR
case ctx.Bool("legacy"):
policyType = wtclientrpc.PolicyType_LEGACY
case ctx.Bool("taproot"):
policyType = wtclientrpc.PolicyType_TAPROOT

// For backwards compatibility with original rpc behavior.
default:
Expand Down
10 changes: 8 additions & 2 deletions docs/release-notes/release-notes-0.18.0.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,13 @@
control to [handle pathfinding errors](https://github.com/lightningnetwork/lnd/pull/8095)
for blinded paths are also included.
* A new config value,
[http-header-timeout](https://github.com/lightningnetwork/lnd/pull/7715), is added so users can specify the amount of time the http server will wait for a request to complete before closing the connection. The default value is 5 seconds.
[http-header-timeout](https://github.com/lightningnetwork/lnd/pull/7715), is
added so users can specify the amount of time the http server will wait for a
request to complete before closing the connection. The default value is 5
seconds.
* Update [watchtowers to be Taproot
ready](https://github.com/lightningnetwork/lnd/pull/7733)


* [`routerrpc.usestatusinitiated` is
introduced](https://github.com/lightningnetwork/lnd/pull/8177) to signal that
Expand Down Expand Up @@ -190,7 +196,7 @@
* [Add a watchtower tower client
multiplexer](https://github.com/lightningnetwork/lnd/pull/7702) to manage
tower clients of different types.

* [Introduce CommitmentType and JusticeKit
interface](https://github.com/lightningnetwork/lnd/pull/7736) to simplify the
code.
Expand Down
46 changes: 31 additions & 15 deletions input/script_utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -2124,27 +2124,14 @@ func NewLocalCommitScriptTree(csvTimeout uint32,

// First, we'll need to construct the tapLeaf that'll be our delay CSV
// clause.
builder := txscript.NewScriptBuilder()
builder.AddData(schnorr.SerializePubKey(selfKey))
builder.AddOp(txscript.OP_CHECKSIG)
builder.AddInt64(int64(csvTimeout))
builder.AddOp(txscript.OP_CHECKSEQUENCEVERIFY)
builder.AddOp(txscript.OP_DROP)

delayScript, err := builder.Script()
delayScript, err := TaprootLocalCommitDelayScript(csvTimeout, selfKey)
if err != nil {
return nil, err
}

// Next, we'll need to construct the revocation path, which is just a
// simple checksig script.
builder = txscript.NewScriptBuilder()
builder.AddData(schnorr.SerializePubKey(selfKey))
builder.AddOp(txscript.OP_DROP)
builder.AddData(schnorr.SerializePubKey(revokeKey))
builder.AddOp(txscript.OP_CHECKSIG)

revokeScript, err := builder.Script()
revokeScript, err := TaprootLocalCommitRevokeScript(selfKey, revokeKey)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -2176,6 +2163,35 @@ func NewLocalCommitScriptTree(csvTimeout uint32,
}, nil
}

// TaprootLocalCommitDelayScript builds the tap leaf with the CSV delay script
// for the to-local output.
func TaprootLocalCommitDelayScript(csvTimeout uint32,
selfKey *btcec.PublicKey) ([]byte, error) {

builder := txscript.NewScriptBuilder()
builder.AddData(schnorr.SerializePubKey(selfKey))
builder.AddOp(txscript.OP_CHECKSIG)
builder.AddInt64(int64(csvTimeout))
builder.AddOp(txscript.OP_CHECKSEQUENCEVERIFY)
builder.AddOp(txscript.OP_DROP)

return builder.Script()
}

// TaprootLocalCommitRevokeScript builds the tap leaf with the revocation path
// for the to-local output.
func TaprootLocalCommitRevokeScript(selfKey, revokeKey *btcec.PublicKey) (
[]byte, error) {

builder := txscript.NewScriptBuilder()
builder.AddData(schnorr.SerializePubKey(selfKey))
builder.AddOp(txscript.OP_DROP)
builder.AddData(schnorr.SerializePubKey(revokeKey))
builder.AddOp(txscript.OP_CHECKSIG)

return builder.Script()
}

// TaprootCommitScriptToSelf creates the taproot witness program that commits
// to the revocation (script path) and delay path (script path) in a single
// taproot output key. Both the delay script and the revocation script are part
Expand Down
60 changes: 26 additions & 34 deletions itest/lnd_revocation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -715,30 +715,24 @@ func testRevokedCloseRetributionRemoteHodl(ht *lntest.HarnessTest) {
// asserts that Willy responds by broadcasting the justice transaction on
// Carol's behalf sweeping her funds without a reward.
func testRevokedCloseRetributionAltruistWatchtower(ht *lntest.HarnessTest) {
testCases := []struct {
name string
anchors bool
}{{
name: "anchors",
anchors: true,
}, {
name: "legacy",
anchors: false,
}}

for _, tc := range testCases {
tc := tc
for _, commitType := range []lnrpc.CommitmentType{
lnrpc.CommitmentType_LEGACY,
lnrpc.CommitmentType_ANCHORS,
lnrpc.CommitmentType_SIMPLE_TAPROOT,
} {
testName := fmt.Sprintf("%v", commitType.String())
ct := commitType
testFunc := func(ht *lntest.HarnessTest) {
testRevokedCloseRetributionAltruistWatchtowerCase(
ht, tc.anchors,
ht, ct,
)
}

success := ht.Run(tc.name, func(tt *testing.T) {
success := ht.Run(testName, func(tt *testing.T) {
st := ht.Subtest(tt)

st.RunTestCase(&lntest.TestCase{
Name: tc.name,
Name: testName,
TestFunc: testFunc,
})
})
Expand All @@ -756,7 +750,7 @@ func testRevokedCloseRetributionAltruistWatchtower(ht *lntest.HarnessTest) {
}

func testRevokedCloseRetributionAltruistWatchtowerCase(ht *lntest.HarnessTest,
anchors bool) {
commitType lnrpc.CommitmentType) {

const (
chanAmt = funding.MaxBtcFundingAmount
Expand All @@ -767,18 +761,19 @@ func testRevokedCloseRetributionAltruistWatchtowerCase(ht *lntest.HarnessTest,

// Since we'd like to test some multi-hop failure scenarios, we'll
// introduce another node into our test network: Carol.
carolArgs := []string{"--hodl.exit-settle"}
if anchors {
carolArgs = append(carolArgs, "--protocol.anchors")
}
carolArgs := lntest.NodeArgsForCommitType(commitType)
carolArgs = append(carolArgs, "--hodl.exit-settle")

carol := ht.NewNode("Carol", carolArgs)

// Willy the watchtower will protect Dave from Carol's breach. He will
// remain online in order to punish Carol on Dave's behalf, since the
// breach will happen while Dave is offline.
willy := ht.NewNode(
"Willy", []string{"--watchtower.active",
"--watchtower.externalip=" + externalIP},
"Willy", []string{
"--watchtower.active",
"--watchtower.externalip=" + externalIP,
},
)

willyInfo := willy.RPC.GetInfoWatchtower()
Expand All @@ -801,13 +796,8 @@ func testRevokedCloseRetributionAltruistWatchtowerCase(ht *lntest.HarnessTest,
// Dave will be the breached party. We set --nolisten to ensure Carol
// won't be able to connect to him and trigger the channel data
// protection logic automatically.
daveArgs := []string{
"--nolisten",
"--wtclient.active",
}
if anchors {
daveArgs = append(daveArgs, "--protocol.anchors")
}
daveArgs := lntest.NodeArgsForCommitType(commitType)
daveArgs = append(daveArgs, "--nolisten", "--wtclient.active")
dave := ht.NewNode("Dave", daveArgs)

addTowerReq := &wtclientrpc.AddTowerRequest{
Expand All @@ -833,8 +823,10 @@ func testRevokedCloseRetributionAltruistWatchtowerCase(ht *lntest.HarnessTest,
// closure by Carol, we'll first open up a channel between them with a
// 0.5 BTC value.
params := lntest.OpenChannelParams{
Amt: 3 * (chanAmt / 4),
PushAmt: chanAmt / 4,
Amt: 3 * (chanAmt / 4),
PushAmt: chanAmt / 4,
CommitmentType: commitType,
Private: true,
}
chanPoint := ht.OpenChannel(dave, carol, params)

Expand Down Expand Up @@ -956,7 +948,7 @@ func testRevokedCloseRetributionAltruistWatchtowerCase(ht *lntest.HarnessTest,
willyBalResp := willy.RPC.WalletBalance()

if willyBalResp.ConfirmedBalance != 0 {
return fmt.Errorf("Expected Willy to have no funds "+
return fmt.Errorf("expected Willy to have no funds "+
"after justice transaction was mined, found %v",
willyBalResp)
}
Expand Down Expand Up @@ -994,7 +986,7 @@ func testRevokedCloseRetributionAltruistWatchtowerCase(ht *lntest.HarnessTest,
ht.AssertNumPendingForceClose(dave, 0)

// If this is an anchor channel, Dave would sweep the anchor.
if anchors {
if lntest.CommitTypeHasAnchors(commitType) {
ht.MineBlocksAndAssertNumTxes(1, 1)
}

Expand Down
56 changes: 41 additions & 15 deletions lnrpc/wtclientrpc/wtclient.go
Original file line number Diff line number Diff line change
Expand Up @@ -273,9 +273,9 @@ func (c *WatchtowerClient) ListTowers(ctx context.Context,
// for the legacy client to the existing tower.
rpcTowers := make(map[wtdb.TowerID]*Tower)
for blobType, towers := range towersPerBlobType {
policyType := PolicyType_LEGACY
if blobType.IsAnchorChannel() {
policyType = PolicyType_ANCHOR
policyType, err := blobTypeToPolicyType(blobType)
if err != nil {
return nil, err
}

for _, tower := range towers {
Expand Down Expand Up @@ -331,9 +331,9 @@ func (c *WatchtowerClient) GetTowerInfo(ctx context.Context,

var resTower *Tower
for blobType, tower := range towersPerBlobType {
policyType := PolicyType_LEGACY
if blobType.IsAnchorChannel() {
policyType = PolicyType_ANCHOR
policyType, err := blobTypeToPolicyType(blobType)
if err != nil {
return nil, err
}

rpcTower := marshallTower(
Expand Down Expand Up @@ -437,15 +437,9 @@ func (c *WatchtowerClient) Policy(ctx context.Context,
return nil, err
}

var blobType blob.Type
switch req.PolicyType {
case PolicyType_LEGACY:
blobType = blob.TypeAltruistCommit
case PolicyType_ANCHOR:
blobType = blob.TypeAltruistAnchorCommit
default:
return nil, fmt.Errorf("unknown policy type: %v",
req.PolicyType)
blobType, err := policyTypeToBlobType(req.PolicyType)
if err != nil {
return nil, err
}

policy, err := c.cfg.ClientMgr.Policy(blobType)
Expand Down Expand Up @@ -525,3 +519,35 @@ func marshallTower(tower *wtclient.RegisteredTower, policyType PolicyType,

return rpcTower
}

func blobTypeToPolicyType(t blob.Type) (PolicyType, error) {
switch t {
case blob.TypeAltruistTaprootCommit:
return PolicyType_TAPROOT, nil

case blob.TypeAltruistAnchorCommit:
return PolicyType_ANCHOR, nil

case blob.TypeAltruistCommit:
return PolicyType_LEGACY, nil

default:
return 0, fmt.Errorf("unknown blob type: %s", t)
}
}

func policyTypeToBlobType(t PolicyType) (blob.Type, error) {
switch t {
case PolicyType_TAPROOT:
return blob.TypeAltruistTaprootCommit, nil

case PolicyType_ANCHOR:
return blob.TypeAltruistAnchorCommit, nil

case PolicyType_LEGACY:
return blob.TypeAltruistCommit, nil

default:
return 0, fmt.Errorf("unknown policy type: %s", t)
}
}
Loading

0 comments on commit 0a29b37

Please sign in to comment.