Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

watchtower: support taproot channel commitments #7733

Merged
merged 11 commits into from
Jan 19, 2024
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
ellemouton marked this conversation as resolved.
Show resolved Hide resolved
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,
ellemouton marked this conversation as resolved.
Show resolved Hide resolved
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
Loading