Merge pull request #7733 from ellemouton/taprootTowers

watchtower: support taproot channel commitments
This commit is contained in:
Elle 2024-01-19 22:55:20 +02:00 committed by GitHub
commit 0a29b37be6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
27 changed files with 1452 additions and 320 deletions

View file

@ -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.",
},
},
}
@ -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:

View file

@ -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
@ -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.

View file

@ -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
}
@ -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

View file

@ -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,
})
})
@ -756,7 +750,7 @@ func testRevokedCloseRetributionAltruistWatchtower(ht *lntest.HarnessTest) {
}
func testRevokedCloseRetributionAltruistWatchtowerCase(ht *lntest.HarnessTest,
anchors bool) {
commitType lnrpc.CommitmentType) {
const (
chanAmt = funding.MaxBtcFundingAmount
@ -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()
@ -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{
@ -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)
@ -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)
}
@ -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)
}

View file

@ -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 {
@ -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(
@ -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)
@ -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)
}
}

View file

@ -27,6 +27,8 @@ const (
PolicyType_LEGACY PolicyType = 0
// Selects the policy from the anchor tower client.
PolicyType_ANCHOR PolicyType = 1
// Selects the policy from the taproot tower client.
PolicyType_TAPROOT PolicyType = 2
)
// Enum value maps for PolicyType.
@ -34,10 +36,12 @@ var (
PolicyType_name = map[int32]string{
0: "LEGACY",
1: "ANCHOR",
2: "TAPROOT",
}
PolicyType_value = map[string]int32{
"LEGACY": 0,
"ANCHOR": 1,
"LEGACY": 0,
"ANCHOR": 1,
"TAPROOT": 2,
}
)
@ -1069,42 +1073,43 @@ var file_wtclientrpc_wtclient_proto_rawDesc = []byte{
0x65, 0x65, 0x70, 0x53, 0x61, 0x74, 0x50, 0x65, 0x72, 0x42, 0x79, 0x74, 0x65, 0x12, 0x2d, 0x0a,
0x13, 0x73, 0x77, 0x65, 0x65, 0x70, 0x5f, 0x73, 0x61, 0x74, 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x76,
0x62, 0x79, 0x74, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x10, 0x73, 0x77, 0x65, 0x65,
0x70, 0x53, 0x61, 0x74, 0x50, 0x65, 0x72, 0x56, 0x62, 0x79, 0x74, 0x65, 0x2a, 0x24, 0x0a, 0x0a,
0x70, 0x53, 0x61, 0x74, 0x50, 0x65, 0x72, 0x56, 0x62, 0x79, 0x74, 0x65, 0x2a, 0x31, 0x0a, 0x0a,
0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x54, 0x79, 0x70, 0x65, 0x12, 0x0a, 0x0a, 0x06, 0x4c, 0x45,
0x47, 0x41, 0x43, 0x59, 0x10, 0x00, 0x12, 0x0a, 0x0a, 0x06, 0x41, 0x4e, 0x43, 0x48, 0x4f, 0x52,
0x10, 0x01, 0x32, 0xc5, 0x03, 0x0a, 0x10, 0x57, 0x61, 0x74, 0x63, 0x68, 0x74, 0x6f, 0x77, 0x65,
0x72, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x12, 0x47, 0x0a, 0x08, 0x41, 0x64, 0x64, 0x54, 0x6f,
0x77, 0x65, 0x72, 0x12, 0x1c, 0x2e, 0x77, 0x74, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x72, 0x70,
0x63, 0x2e, 0x41, 0x64, 0x64, 0x54, 0x6f, 0x77, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
0x74, 0x1a, 0x1d, 0x2e, 0x77, 0x74, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x72, 0x70, 0x63, 0x2e,
0x41, 0x64, 0x64, 0x54, 0x6f, 0x77, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
0x12, 0x50, 0x0a, 0x0b, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x54, 0x6f, 0x77, 0x65, 0x72, 0x12,
0x1f, 0x2e, 0x77, 0x74, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65,
0x6d, 0x6f, 0x76, 0x65, 0x54, 0x6f, 0x77, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
0x1a, 0x20, 0x2e, 0x77, 0x74, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x52,
0x65, 0x6d, 0x6f, 0x76, 0x65, 0x54, 0x6f, 0x77, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
0x73, 0x65, 0x12, 0x4d, 0x0a, 0x0a, 0x4c, 0x69, 0x73, 0x74, 0x54, 0x6f, 0x77, 0x65, 0x72, 0x73,
0x12, 0x1e, 0x2e, 0x77, 0x74, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4c,
0x69, 0x73, 0x74, 0x54, 0x6f, 0x77, 0x65, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
0x1a, 0x1f, 0x2e, 0x77, 0x74, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4c,
0x69, 0x73, 0x74, 0x54, 0x6f, 0x77, 0x65, 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
0x65, 0x12, 0x44, 0x0a, 0x0c, 0x47, 0x65, 0x74, 0x54, 0x6f, 0x77, 0x65, 0x72, 0x49, 0x6e, 0x66,
0x6f, 0x12, 0x20, 0x2e, 0x77, 0x74, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x72, 0x70, 0x63, 0x2e,
0x47, 0x65, 0x74, 0x54, 0x6f, 0x77, 0x65, 0x72, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71, 0x75,
0x65, 0x73, 0x74, 0x1a, 0x12, 0x2e, 0x77, 0x74, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x72, 0x70,
0x63, 0x2e, 0x54, 0x6f, 0x77, 0x65, 0x72, 0x12, 0x3e, 0x0a, 0x05, 0x53, 0x74, 0x61, 0x74, 0x73,
0x12, 0x19, 0x2e, 0x77, 0x74, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x53,
0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x77, 0x74,
0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52,
0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x41, 0x0a, 0x06, 0x50, 0x6f, 0x6c, 0x69, 0x63,
0x79, 0x12, 0x1a, 0x2e, 0x77, 0x74, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x72, 0x70, 0x63, 0x2e,
0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e,
0x77, 0x74, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x6f, 0x6c, 0x69,
0x63, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x33, 0x5a, 0x31, 0x67, 0x69,
0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x6e, 0x69,
0x6e, 0x67, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x2f, 0x6c, 0x6e, 0x64, 0x2f, 0x6c, 0x6e,
0x72, 0x70, 0x63, 0x2f, 0x77, 0x74, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x72, 0x70, 0x63, 0x62,
0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
0x10, 0x01, 0x12, 0x0b, 0x0a, 0x07, 0x54, 0x41, 0x50, 0x52, 0x4f, 0x4f, 0x54, 0x10, 0x02, 0x32,
0xc5, 0x03, 0x0a, 0x10, 0x57, 0x61, 0x74, 0x63, 0x68, 0x74, 0x6f, 0x77, 0x65, 0x72, 0x43, 0x6c,
0x69, 0x65, 0x6e, 0x74, 0x12, 0x47, 0x0a, 0x08, 0x41, 0x64, 0x64, 0x54, 0x6f, 0x77, 0x65, 0x72,
0x12, 0x1c, 0x2e, 0x77, 0x74, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x41,
0x64, 0x64, 0x54, 0x6f, 0x77, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d,
0x2e, 0x77, 0x74, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x64, 0x64,
0x54, 0x6f, 0x77, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x50, 0x0a,
0x0b, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x54, 0x6f, 0x77, 0x65, 0x72, 0x12, 0x1f, 0x2e, 0x77,
0x74, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x6d, 0x6f, 0x76,
0x65, 0x54, 0x6f, 0x77, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e,
0x77, 0x74, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x6d, 0x6f,
0x76, 0x65, 0x54, 0x6f, 0x77, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12,
0x4d, 0x0a, 0x0a, 0x4c, 0x69, 0x73, 0x74, 0x54, 0x6f, 0x77, 0x65, 0x72, 0x73, 0x12, 0x1e, 0x2e,
0x77, 0x74, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74,
0x54, 0x6f, 0x77, 0x65, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e,
0x77, 0x74, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x69, 0x73, 0x74,
0x54, 0x6f, 0x77, 0x65, 0x72, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x44,
0x0a, 0x0c, 0x47, 0x65, 0x74, 0x54, 0x6f, 0x77, 0x65, 0x72, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x20,
0x2e, 0x77, 0x74, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x65, 0x74,
0x54, 0x6f, 0x77, 0x65, 0x72, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
0x1a, 0x12, 0x2e, 0x77, 0x74, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x54,
0x6f, 0x77, 0x65, 0x72, 0x12, 0x3e, 0x0a, 0x05, 0x53, 0x74, 0x61, 0x74, 0x73, 0x12, 0x19, 0x2e,
0x77, 0x74, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x74, 0x61, 0x74,
0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x77, 0x74, 0x63, 0x6c, 0x69,
0x65, 0x6e, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70,
0x6f, 0x6e, 0x73, 0x65, 0x12, 0x41, 0x0a, 0x06, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x12, 0x1a,
0x2e, 0x77, 0x74, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x6f, 0x6c,
0x69, 0x63, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x77, 0x74, 0x63,
0x6c, 0x69, 0x65, 0x6e, 0x74, 0x72, 0x70, 0x63, 0x2e, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52,
0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x33, 0x5a, 0x31, 0x67, 0x69, 0x74, 0x68, 0x75,
0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6c, 0x69, 0x67, 0x68, 0x74, 0x6e, 0x69, 0x6e, 0x67, 0x6e,
0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x2f, 0x6c, 0x6e, 0x64, 0x2f, 0x6c, 0x6e, 0x72, 0x70, 0x63,
0x2f, 0x77, 0x74, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x72, 0x70, 0x63, 0x62, 0x06, 0x70, 0x72,
0x6f, 0x74, 0x6f, 0x33,
}
var (

View file

@ -219,6 +219,9 @@ enum PolicyType {
// Selects the policy from the anchor tower client.
ANCHOR = 1;
// Selects the policy from the taproot tower client.
TAPROOT = 2;
}
message PolicyRequest {

View file

@ -154,13 +154,14 @@
"parameters": [
{
"name": "policy_type",
"description": "The client type from which to retrieve the active offering policy.\n\n - LEGACY: Selects the policy from the legacy tower client.\n - ANCHOR: Selects the policy from the anchor tower client.",
"description": "The client type from which to retrieve the active offering policy.\n\n - LEGACY: Selects the policy from the legacy tower client.\n - ANCHOR: Selects the policy from the anchor tower client.\n - TAPROOT: Selects the policy from the taproot tower client.",
"in": "query",
"required": false,
"type": "string",
"enum": [
"LEGACY",
"ANCHOR"
"ANCHOR",
"TAPROOT"
],
"default": "LEGACY"
}
@ -318,10 +319,11 @@
"type": "string",
"enum": [
"LEGACY",
"ANCHOR"
"ANCHOR",
"TAPROOT"
],
"default": "LEGACY",
"description": " - LEGACY: Selects the policy from the legacy tower client.\n - ANCHOR: Selects the policy from the anchor tower client."
"description": " - LEGACY: Selects the policy from the legacy tower client.\n - ANCHOR: Selects the policy from the anchor tower client.\n - TAPROOT: Selects the policy from the taproot tower client."
},
"wtclientrpcRemoveTowerResponse": {
"type": "object"

View file

@ -1043,24 +1043,6 @@ func (p *Brontide) addLink(chanPoint *wire.OutPoint,
return p.cfg.ChainArb.NotifyContractUpdate(*chanPoint, update)
}
var towerClient wtclient.ClientManager
if lnChan.ChanType().IsTaproot() {
// Leave the tower client as nil for now until the tower client
// has support for taproot channels.
//
// If the user has activated the tower client, then add a log
// to explain that any taproot channel updates wil not be
// backed up to a tower.
if p.cfg.TowerClient != nil {
p.log.Debugf("Updates for channel %s will not be "+
"backed up to a watchtower as watchtowers "+
"are not yet taproot channel compatible",
chanPoint)
}
} else {
towerClient = p.cfg.TowerClient
}
//nolint:lll
linkCfg := htlcswitch.ChannelLinkConfig{
Peer: p,
@ -1090,7 +1072,7 @@ func (p *Brontide) addLink(chanPoint *wire.OutPoint,
MinFeeUpdateTimeout: htlcswitch.DefaultMinLinkFeeUpdateTimeout,
MaxFeeUpdateTimeout: htlcswitch.DefaultMaxLinkFeeUpdateTimeout,
OutgoingCltvRejectDelta: p.cfg.OutgoingCltvRejectDelta,
TowerClient: towerClient,
TowerClient: p.cfg.TowerClient,
MaxOutgoingCltvExpiry: p.cfg.MaxOutgoingCltvExpiry,
MaxFeeAllocation: p.cfg.MaxChannelFeeAllocation,
MaxAnchorsCommitFeeRate: p.cfg.MaxAnchorsCommitFeeRate,

View file

@ -1553,6 +1553,13 @@ func newServer(cfg *Config, listenAddrs []net.Addr,
anchorPolicy := policy
anchorPolicy.BlobType |= blob.Type(blob.FlagAnchorChannel)
// Copy the policy for legacy channels and set the blob flag
// signalling support for taproot channels.
taprootPolicy := policy
taprootPolicy.TxPolicy.BlobType |= blob.Type(
blob.FlagTaprootChannel,
)
s.towerClientMgr, err = wtclient.NewManager(&wtclient.Config{
FetchClosedChannel: fetchClosedChannel,
BuildBreachRetribution: buildBreachRetribution,
@ -1574,7 +1581,7 @@ func newServer(cfg *Config, listenAddrs []net.Addr,
MinBackoff: 10 * time.Second,
MaxBackoff: 5 * time.Minute,
MaxTasksInMemQueue: cfg.WtClient.MaxTasksInMemQueue,
}, policy, anchorPolicy)
}, policy, anchorPolicy, taprootPolicy)
if err != nil {
return nil, err
}

View file

@ -27,6 +27,10 @@ const (
// anchor channel. The key differences are that the to_remote is
// encumbered by a 1 block CSV and so is thus a P2WSH output.
AnchorCommitment
// TaprootCommitment represents the commitment transaction of a simple
// taproot channel.
TaprootCommitment
)
// ToLocalInput constructs the input that will be used to spend the to_local
@ -61,9 +65,10 @@ func (c CommitmentType) ToRemoteInput(info *lnwallet.BreachRetribution) (
info.LocalOutputSignDesc, 0,
), nil
case AnchorCommitment:
// Anchor channels have a CSV-encumbered to-remote output. We'll
// construct a CSV input and assign the proper CSV delay of 1.
case AnchorCommitment, TaprootCommitment:
// Anchor and Taproot channels have a CSV-encumbered to-remote
// output. We'll construct a CSV input and assign the proper CSV
// delay of 1.
return input.NewCsvInput(
&info.LocalOutpoint, witnessType,
info.LocalOutputSignDesc, 0, 1,
@ -80,6 +85,9 @@ func (c CommitmentType) ToLocalWitnessType() (input.WitnessType, error) {
case LegacyTweaklessCommitment, LegacyCommitment, AnchorCommitment:
return input.CommitmentRevoke, nil
case TaprootCommitment:
return input.TaprootCommitmentRevoke, nil
default:
return nil, fmt.Errorf("unknown commitment type: %v", c)
}
@ -97,6 +105,9 @@ func (c CommitmentType) ToRemoteWitnessType() (input.WitnessType, error) {
case AnchorCommitment:
return input.CommitmentToRemoteConfirmed, nil
case TaprootCommitment:
return input.TaprootRemoteCommitSpend, nil
default:
return nil, fmt.Errorf("unknown commitment type: %v", c)
}
@ -115,6 +126,10 @@ func (c CommitmentType) ToRemoteWitnessSize() (int, error) {
case AnchorCommitment:
return input.ToRemoteConfirmedWitnessSize, nil
// Taproot channels spend a confirmed P2SH output.
case TaprootCommitment:
return input.TaprootToRemoteWitnessSize, nil
default:
return 0, fmt.Errorf("unknown commitment type: %v", c)
}
@ -134,6 +149,9 @@ func (c CommitmentType) ToLocalWitnessSize() (int, error) {
case AnchorCommitment:
return input.ToLocalPenaltyWitnessSize, nil
case TaprootCommitment:
return input.TaprootToLocalRevokeWitnessSize, nil
default:
return 0, fmt.Errorf("unknown commitment type: %v", c)
}
@ -143,21 +161,22 @@ func (c CommitmentType) ToLocalWitnessSize() (int, error) {
func (c CommitmentType) ParseRawSig(witness wire.TxWitness) (lnwire.Sig,
error) {
// Check that the witness has at least one item since this is required
// for all commitment types to follow.
if len(witness) < 1 {
return lnwire.Sig{}, fmt.Errorf("the witness should have at " +
"least one element")
}
// Check that the first witness element is non-nil. This is to ensure
// that the witness length checks below do not panic.
if witness[0] == nil {
return lnwire.Sig{}, fmt.Errorf("the first witness element " +
"should not be nil")
}
switch c {
case LegacyCommitment, LegacyTweaklessCommitment, AnchorCommitment:
// Check that the witness has at least one item.
if len(witness) < 1 {
return lnwire.Sig{}, fmt.Errorf("the witness should " +
"have at least one element")
}
// Check that the first witness element is non-nil. This is to
// ensure that the witness length check below does not panic.
if witness[0] == nil {
return lnwire.Sig{}, fmt.Errorf("the first witness " +
"element should not be nil")
}
// Parse the DER-encoded signature from the first position of
// the resulting witness. We trim an extra byte to remove the
// sighash flag.
@ -167,6 +186,16 @@ func (c CommitmentType) ParseRawSig(witness wire.TxWitness) (lnwire.Sig,
// signature.
return lnwire.NewSigFromECDSARawSignature(rawSignature)
case TaprootCommitment:
rawSignature := witness[0]
if len(rawSignature) > 64 {
rawSignature = witness[0][:len(witness[0])-1]
}
// Re-encode the schnorr signature into a fixed-size 64 byte
// signature.
return lnwire.NewSigFromSchnorrRawSignature(rawSignature)
default:
return lnwire.Sig{}, fmt.Errorf("unknown commitment type: %v",
c)
@ -190,6 +219,11 @@ func (c CommitmentType) NewJusticeKit(sweepScript []byte,
sweepScript, breachInfo, withToRemote,
), nil
case TaprootCommitment:
return newTaprootJusticeKit(
sweepScript, breachInfo, withToRemote,
)
default:
return nil, fmt.Errorf("unknown commitment type: %v", c)
}
@ -207,6 +241,9 @@ func (c CommitmentType) EmptyJusticeKit() (JusticeKit, error) {
legacyJusticeKit: legacyJusticeKit{},
}, nil
case TaprootCommitment:
return &taprootJusticeKit{}, nil
default:
return nil, fmt.Errorf("unknown commitment type: %v", c)
}

View file

@ -1,11 +1,15 @@
package blob
import (
"bytes"
"io"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/btcec/v2/schnorr"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcd/wire"
secp "github.com/decred/dcrd/dcrec/secp256k1/v4"
"github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/lnwallet"
"github.com/lightningnetwork/lnd/lnwire"
@ -69,11 +73,10 @@ func newLegacyJusticeKit(sweepScript []byte,
keyRing := breachInfo.KeyRing
packet := justiceKitPacketV0{
sweepAddress: sweepScript,
revocationPubKey: toBlobPubKey(keyRing.RevocationKey),
localDelayPubKey: toBlobPubKey(keyRing.ToLocalKey),
csvDelay: breachInfo.RemoteDelay,
commitToRemotePubKey: pubKey{},
sweepAddress: sweepScript,
revocationPubKey: toBlobPubKey(keyRing.RevocationKey),
localDelayPubKey: toBlobPubKey(keyRing.ToLocalKey),
csvDelay: breachInfo.RemoteDelay,
}
if withToRemote {
@ -286,3 +289,214 @@ func (a *anchorJusticeKit) ToRemoteOutputSpendInfo() (*txscript.PkScript,
return &pkScript, witness, 1, nil
}
// taprootJusticeKit is an implementation of the JusticeKit interface which can
// be used for backing up commitments of taproot channels.
type taprootJusticeKit struct {
justiceKitPacketV1
}
// A compile-time check to ensure that taprootJusticeKit implements the
// JusticeKit interface.
var _ JusticeKit = (*taprootJusticeKit)(nil)
// newTaprootJusticeKit constructs a new taprootJusticeKit.
func newTaprootJusticeKit(sweepScript []byte,
breachInfo *lnwallet.BreachRetribution,
withToRemote bool) (*taprootJusticeKit, error) {
keyRing := breachInfo.KeyRing
tree, err := input.NewLocalCommitScriptTree(
breachInfo.RemoteDelay, keyRing.ToLocalKey,
keyRing.RevocationKey,
)
if err != nil {
return nil, err
}
packet := justiceKitPacketV1{
sweepAddress: sweepScript,
revocationPubKey: toBlobSchnorrPubKey(
keyRing.RevocationKey,
),
localDelayPubKey: toBlobSchnorrPubKey(keyRing.ToLocalKey),
delayScriptHash: tree.SettleLeaf.TapHash(),
}
if withToRemote {
packet.commitToRemotePubKey = toBlobPubKey(keyRing.ToRemoteKey)
}
return &taprootJusticeKit{packet}, nil
}
// ToLocalOutputSpendInfo returns the info required to send the to-local
// output. It returns the output pubkey script and the witness required
// to spend the output.
//
// NOTE: This is part of the JusticeKit interface.
func (t *taprootJusticeKit) ToLocalOutputSpendInfo() (*txscript.PkScript,
wire.TxWitness, error) {
revocationPubKey, err := schnorr.ParsePubKey(t.revocationPubKey[:])
if err != nil {
return nil, nil, err
}
localDelayedPubKey, err := schnorr.ParsePubKey(t.localDelayPubKey[:])
if err != nil {
return nil, nil, err
}
revokeScript, err := input.TaprootLocalCommitRevokeScript(
localDelayedPubKey, revocationPubKey,
)
if err != nil {
return nil, nil, err
}
revokeLeaf := txscript.NewBaseTapLeaf(revokeScript)
revokeLeafHash := revokeLeaf.TapHash()
rootHash := tapBranchHash(revokeLeafHash[:], t.delayScriptHash[:])
outputKey := txscript.ComputeTaprootOutputKey(
&input.TaprootNUMSKey, rootHash[:],
)
scriptPk, err := input.PayToTaprootScript(outputKey)
if err != nil {
return nil, nil, err
}
ctrlBlock := txscript.ControlBlock{
InternalKey: &input.TaprootNUMSKey,
OutputKeyYIsOdd: isOddPub(outputKey),
LeafVersion: revokeLeaf.LeafVersion,
InclusionProof: t.delayScriptHash[:],
}
ctrlBytes, err := ctrlBlock.ToBytes()
if err != nil {
return nil, nil, err
}
toLocalSig, err := t.commitToLocalSig.ToSignature()
if err != nil {
return nil, nil, err
}
witness := make([][]byte, 3)
witness[0] = toLocalSig.Serialize()
witness[1] = revokeScript
witness[2] = ctrlBytes
pkScript, err := txscript.ParsePkScript(scriptPk)
if err != nil {
return nil, nil, err
}
return &pkScript, witness, nil
}
// ToRemoteOutputSpendInfo returns the info required to send the to-remote
// output. It returns the output pubkey script, the witness required to spend
// the output and the sequence to apply.
//
// NOTE: This is part of the JusticeKit interface.
func (t *taprootJusticeKit) ToRemoteOutputSpendInfo() (*txscript.PkScript,
wire.TxWitness, uint32, error) {
if len(t.commitToRemotePubKey[:]) == 0 {
return nil, nil, 0, ErrNoCommitToRemoteOutput
}
toRemotePk, err := btcec.ParsePubKey(t.commitToRemotePubKey[:])
if err != nil {
return nil, nil, 0, err
}
scriptTree, err := input.NewRemoteCommitScriptTree(toRemotePk)
if err != nil {
return nil, nil, 0, err
}
script, err := input.PayToTaprootScript(scriptTree.TaprootKey)
if err != nil {
return nil, nil, 0, err
}
settleControlBlock := input.MakeTaprootCtrlBlock(
scriptTree.SettleLeaf.Script, &input.TaprootNUMSKey,
scriptTree.TapscriptTree,
)
ctrl, err := settleControlBlock.ToBytes()
if err != nil {
return nil, nil, 0, err
}
toRemoteSig, err := t.commitToRemoteSig.ToSignature()
if err != nil {
return nil, nil, 0, err
}
witness := make([][]byte, 3)
witness[0] = toRemoteSig.Serialize()
witness[1] = scriptTree.SettleLeaf.Script
witness[2] = ctrl
pkScript, err := txscript.ParsePkScript(script)
if err != nil {
return nil, nil, 0, err
}
return &pkScript, witness, 1, nil
}
// HasCommitToRemoteOutput returns true if the blob contains a to-remote pubkey.
//
// NOTE: This is part of the JusticeKit interface.
func (t *taprootJusticeKit) HasCommitToRemoteOutput() bool {
return btcec.IsCompressedPubKey(t.commitToRemotePubKey[:])
}
// AddToLocalSig adds the to-local signature to the kit.
//
// NOTE: This is part of the JusticeKit interface.
func (t *taprootJusticeKit) AddToLocalSig(sig lnwire.Sig) {
t.commitToLocalSig = sig
}
// AddToRemoteSig adds the to-remote signature to the kit.
//
// NOTE: This is part of the JusticeKit interface.
func (t *taprootJusticeKit) AddToRemoteSig(sig lnwire.Sig) {
t.commitToRemoteSig = sig
}
// SweepAddress returns the sweep address to be used on the justice tx output.
//
// NOTE: This is part of the JusticeKit interface.
func (t *taprootJusticeKit) SweepAddress() []byte {
return t.sweepAddress
}
// PlainTextSize is the size of the encoded-but-unencrypted blob in bytes.
//
// NOTE: This is part of the JusticeKit interface.
func (t *taprootJusticeKit) PlainTextSize() int {
return V1PlaintextSize
}
func tapBranchHash(l, r []byte) chainhash.Hash {
if bytes.Compare(l, r) > 0 {
l, r = r, l
}
return *chainhash.TaggedHash(chainhash.TagTapBranch, l, r)
}
func isOddPub(key *btcec.PublicKey) bool {
return key.SerializeCompressed()[0] == secp.PubKeyFormatCompressedOdd
}

View file

@ -9,6 +9,8 @@ import (
"io"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/btcec/v2/schnorr"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/lightningnetwork/lnd/lnwire"
"golang.org/x/crypto/chacha20poly1305"
)
@ -35,6 +37,17 @@ const (
// commit to-remote sig: 64 bytes, maybe blank
V0PlaintextSize = 274
// V1PlaintextSize is the plaintext size of a version 1 encoded blob.
// sweep address length: 1 byte
// padded sweep address: 42 bytes
// revocation pubkey: 32 bytes
// local delay pubkey: 32 bytes
// commit to-local revocation sig: 64 bytes
// hash of to-local delay script: 32 bytes
// commit to-remote pubkey: 33 bytes, maybe blank
// commit to-remote sig: 64 bytes, maybe blank
V1PlaintextSize = 300
// MaxSweepAddrSize defines the maximum sweep address size that can be
// encoded in a blob.
MaxSweepAddrSize = 42
@ -78,6 +91,17 @@ func Size(kit JusticeKit) int {
return NonceSize + kit.PlainTextSize() + CiphertextExpansion
}
// schnorrPubKey is a 32-byte serialized x-only public key.
type schnorrPubKey [32]byte
// toBlobSchnorrPubKey serializes the given public key into a schnorrPubKey that
// can be set as a field on a JusticeKit.
func toBlobSchnorrPubKey(pubKey *btcec.PublicKey) schnorrPubKey {
var blobPubKey schnorrPubKey
copy(blobPubKey[:], schnorr.SerializePubKey(pubKey))
return blobPubKey
}
// pubKey is a 33-byte, serialized compressed public key.
type pubKey [33]byte
@ -400,3 +424,222 @@ func (b *justiceKitPacketV0) decode(r io.Reader) error {
return nil
}
// justiceKitPacketV1 is the Blob of Justice for taproot channels.
type justiceKitPacketV1 struct {
// sweepAddress is the witness program of the output where the client's
// fund will be deposited. This value is included in the blobs, as
// opposed to the session info, such that the sweep addresses can't be
// correlated across sessions and/or towers.
//
// NOTE: This is chosen to be the length of a maximally sized witness
// program.
sweepAddress []byte
// revocationPubKey is the x-only pubkey that guards the revocation
// clause of the remote party's to-local output.
revocationPubKey schnorrPubKey
// localDelayPubKey is the x-only pubkey in the to-local script of
// the remote party, which guards the path where the remote party
// claims their commitment output.
localDelayPubKey schnorrPubKey
// delayScriptHash is the hash of the to_local delay script that is used
// in the TapTree.
delayScriptHash [chainhash.HashSize]byte
// commitToLocalSig is a signature under revocationPubKey using
// SIGHASH_DEFAULT.
commitToLocalSig lnwire.Sig
// commitToRemotePubKey is the public key in the to-remote output of the
// revoked commitment transaction. This uses a 33-byte compressed pubkey
// encoding unlike the other public keys because it will not always be
// present and so this gives us an easy way to check if it is present or
// not.
//
// NOTE: This value is only used if it contains a valid compressed
// public key.
commitToRemotePubKey pubKey
// commitToRemoteSig is a signature under commitToRemotePubKey using
// SIGHASH_DEFAULT.
//
// NOTE: This value is only used if commitToRemotePubKey contains a
// valid compressed public key.
commitToRemoteSig lnwire.Sig
}
// encode encodes the justiceKitPacketV1 to the provided io.Writer. The encoding
// supports sweeping of the commit to-local output, and optionally the commit
// to-remote output. The encoding produces a constant-size plaintext size of
// 300 bytes.
//
// blob version 1 plaintext encoding:
//
// sweep address length: 1 byte
// padded sweep address: 42 bytes
// revocation pubkey: 32 bytes
// local delay pubkey: 32 bytes
// commit to-local revocation sig: 64 bytes
// hash of to-local delay script: 32 bytes
// commit to-remote pubkey: 33 bytes, maybe blank
// commit to-remote sig: 64 bytes, maybe blank
func (t *justiceKitPacketV1) encode(w io.Writer) error {
// Assert the sweep address length is sane.
if len(t.sweepAddress) > MaxSweepAddrSize {
return ErrSweepAddressToLong
}
// Write the actual length of the sweep address as a single byte.
err := binary.Write(w, byteOrder, uint8(len(t.sweepAddress)))
if err != nil {
return err
}
// Pad the sweep address to our maximum length of 42 bytes.
var sweepAddressBuf [MaxSweepAddrSize]byte
copy(sweepAddressBuf[:], t.sweepAddress)
// Write padded 42-byte sweep address.
_, err = w.Write(sweepAddressBuf[:])
if err != nil {
return err
}
// Write 32-byte revocation public key.
_, err = w.Write(t.revocationPubKey[:])
if err != nil {
return err
}
// Write 32-byte local delay public key.
_, err = w.Write(t.localDelayPubKey[:])
if err != nil {
return err
}
// Write 64-byte revocation signature for commit to-local output.
_, err = w.Write(t.commitToLocalSig.RawBytes())
if err != nil {
return err
}
// Write 32-byte hash of the to-local delay script.
_, err = w.Write(t.delayScriptHash[:])
if err != nil {
return err
}
// Write 33-byte commit to-remote public key, which may be blank.
_, err = w.Write(t.commitToRemotePubKey[:])
if err != nil {
return err
}
// Write 64-byte commit to-remote signature, which may be blank.
_, err = w.Write(t.commitToRemoteSig.RawBytes())
return err
}
// decode reconstructs a justiceKitPacketV1 from the io.Reader, using version 1
// encoding scheme. This will parse a constant size input stream of 300 bytes to
// recover information for the commit to-local output, and possibly the commit
// to-remote output.
//
// blob version 1 plaintext encoding:
//
// sweep address length: 1 byte
// padded sweep address: 42 bytes
// revocation pubkey: 32 bytes
// local delay pubkey: 32 bytes
// commit to-local revocation sig: 64 bytes
// hash of to-local delay script: 32 bytes
// commit to-remote pubkey: 33 bytes, maybe blank
// commit to-remote sig: 64 bytes, maybe blank
func (t *justiceKitPacketV1) decode(r io.Reader) error {
// Read the sweep address length as a single byte.
var sweepAddrLen uint8
err := binary.Read(r, byteOrder, &sweepAddrLen)
if err != nil {
return err
}
// Assert the sweep address length is sane.
if sweepAddrLen > MaxSweepAddrSize {
return ErrSweepAddressToLong
}
// Read padded 42-byte sweep address.
var sweepAddressBuf [MaxSweepAddrSize]byte
_, err = io.ReadFull(r, sweepAddressBuf[:])
if err != nil {
return err
}
// Parse sweep address from padded buffer.
t.sweepAddress = make([]byte, sweepAddrLen)
copy(t.sweepAddress, sweepAddressBuf[:])
// Read 32-byte revocation public key.
_, err = io.ReadFull(r, t.revocationPubKey[:])
if err != nil {
return err
}
// Read 32-byte local delay public key.
_, err = io.ReadFull(r, t.localDelayPubKey[:])
if err != nil {
return err
}
// Read 64-byte revocation signature for commit to-local output.
var localSig [64]byte
_, err = io.ReadFull(r, localSig[:])
if err != nil {
return err
}
// Read 32-byte to-local delay script hash.
_, err = io.ReadFull(r, t.delayScriptHash[:])
if err != nil {
return err
}
t.commitToLocalSig, err = lnwire.NewSigFromSchnorrRawSignature(
localSig[:],
)
if err != nil {
return err
}
var (
commitToRemotePubkey pubKey
commitToRemoteSig [64]byte
)
// Read 33-byte commit to-remote public key, which may be discarded.
_, err = io.ReadFull(r, commitToRemotePubkey[:])
if err != nil {
return err
}
// Read 64-byte commit to-remote signature, which may be discarded.
_, err = io.ReadFull(r, commitToRemoteSig[:])
if err != nil {
return err
}
// Only populate the commit to-remote fields in the decoded blob if a
// valid compressed public key was read from the reader.
if !btcec.IsCompressedPubKey(commitToRemotePubkey[:]) {
return nil
}
t.commitToRemotePubKey = commitToRemotePubkey
t.commitToRemoteSig, err = lnwire.NewSigFromSchnorrRawSignature(
commitToRemoteSig[:],
)
if err != nil {
return err
}
return nil
}

View file

@ -9,6 +9,7 @@ import (
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/btcec/v2/ecdsa"
"github.com/btcsuite/btcd/btcec/v2/schnorr"
"github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcd/wire"
"github.com/lightningnetwork/lnd/input"
@ -17,6 +18,8 @@ import (
"github.com/stretchr/testify/require"
)
const csvDelay = uint32(144)
func makePubKey() *btcec.PublicKey {
priv, _ := btcec.NewPrivateKey()
return priv.PubKey()
@ -39,6 +42,15 @@ func makeAddr(size int) []byte {
return addr
}
func makeSchnorrSig(i int) lnwire.Sig {
var sigBytes [64]byte
binary.BigEndian.PutUint64(sigBytes[:8], uint64(i))
sig, _ := lnwire.NewSigFromSchnorrRawSignature(sigBytes[:])
return sig
}
type descriptorTest struct {
name string
encVersion Type
@ -46,7 +58,6 @@ type descriptorTest struct {
sweepAddr []byte
revPubKey *btcec.PublicKey
delayPubKey *btcec.PublicKey
csvDelay uint32
commitToLocalSig lnwire.Sig
hasCommitToRemote bool
commitToRemotePubKey *btcec.PublicKey
@ -63,7 +74,6 @@ var descriptorTests = []descriptorTest{
sweepAddr: makeAddr(22),
revPubKey: makePubKey(),
delayPubKey: makePubKey(),
csvDelay: 144,
commitToLocalSig: makeSig(1),
},
{
@ -73,7 +83,6 @@ var descriptorTests = []descriptorTest{
sweepAddr: makeAddr(22),
revPubKey: makePubKey(),
delayPubKey: makePubKey(),
csvDelay: 144,
commitToLocalSig: makeSig(1),
hasCommitToRemote: true,
commitToRemotePubKey: makePubKey(),
@ -86,7 +95,6 @@ var descriptorTests = []descriptorTest{
sweepAddr: makeAddr(34),
revPubKey: makePubKey(),
delayPubKey: makePubKey(),
csvDelay: 144,
commitToLocalSig: makeSig(1),
encErr: ErrUnknownBlobType,
},
@ -97,7 +105,6 @@ var descriptorTests = []descriptorTest{
sweepAddr: makeAddr(34),
revPubKey: makePubKey(),
delayPubKey: makePubKey(),
csvDelay: 144,
commitToLocalSig: makeSig(1),
decErr: ErrUnknownBlobType,
},
@ -108,7 +115,6 @@ var descriptorTests = []descriptorTest{
sweepAddr: makeAddr(0),
revPubKey: makePubKey(),
delayPubKey: makePubKey(),
csvDelay: 144,
commitToLocalSig: makeSig(1),
},
{
@ -118,7 +124,6 @@ var descriptorTests = []descriptorTest{
sweepAddr: makeAddr(MaxSweepAddrSize),
revPubKey: makePubKey(),
delayPubKey: makePubKey(),
csvDelay: 144,
commitToLocalSig: makeSig(1),
},
{
@ -128,10 +133,30 @@ var descriptorTests = []descriptorTest{
sweepAddr: makeAddr(MaxSweepAddrSize + 1),
revPubKey: makePubKey(),
delayPubKey: makePubKey(),
csvDelay: 144,
commitToLocalSig: makeSig(1),
encErr: ErrSweepAddressToLong,
},
{
name: "taproot to-local only",
encVersion: TypeAltruistTaprootCommit,
decVersion: TypeAltruistTaprootCommit,
sweepAddr: makeAddr(34),
revPubKey: makePubKey(),
delayPubKey: makePubKey(),
commitToLocalSig: makeSchnorrSig(1),
},
{
name: "taproot to-local and to-remote",
encVersion: TypeAltruistTaprootCommit,
decVersion: TypeAltruistTaprootCommit,
sweepAddr: makeAddr(34),
revPubKey: makePubKey(),
delayPubKey: makePubKey(),
commitToLocalSig: makeSchnorrSig(1),
hasCommitToRemote: true,
commitToRemotePubKey: makePubKey(),
commitToRemoteSig: makeSchnorrSig(2),
},
}
// TestBlobJusticeKitEncryptDecrypt asserts that encrypting and decrypting a
@ -154,7 +179,7 @@ func testBlobJusticeKitEncryptDecrypt(t *testing.T, test descriptorTest) {
}
breachInfo := &lnwallet.BreachRetribution{
RemoteDelay: test.csvDelay,
RemoteDelay: csvDelay,
KeyRing: &lnwallet.CommitmentKeyRing{
ToLocalKey: test.delayPubKey,
ToRemoteKey: test.commitToRemotePubKey,
@ -221,6 +246,8 @@ type remoteWitnessTest struct {
name string
blobType Type
expWitnessScript func(pk *btcec.PublicKey) []byte
expWitnessStack func(sig input.Signature) wire.TxWitness
createSig func(*btcec.PrivateKey, []byte) input.Signature
}
// TestJusticeKitRemoteWitnessConstruction tests that a JusticeKit returns the
@ -234,6 +261,21 @@ func TestJusticeKitRemoteWitnessConstruction(t *testing.T) {
expWitnessScript: func(pk *btcec.PublicKey) []byte {
return pk.SerializeCompressed()
},
expWitnessStack: func(
sig input.Signature) wire.TxWitness {
sigBytes := append(
sig.Serialize(),
byte(txscript.SigHashAll),
)
return [][]byte{sigBytes}
},
createSig: func(priv *btcec.PrivateKey,
digest []byte) input.Signature {
return ecdsa.Sign(priv, digest)
},
},
{
name: "anchor commitment",
@ -242,6 +284,42 @@ func TestJusticeKitRemoteWitnessConstruction(t *testing.T) {
script, _ := input.CommitScriptToRemoteConfirmed(pk)
return script
},
expWitnessStack: func(
sig input.Signature) wire.TxWitness {
sigBytes := append(
sig.Serialize(),
byte(txscript.SigHashAll),
)
return [][]byte{sigBytes}
},
createSig: func(priv *btcec.PrivateKey,
digest []byte) input.Signature {
return ecdsa.Sign(priv, digest)
},
},
{
name: "taproot commitment",
blobType: TypeAltruistTaprootCommit,
expWitnessScript: func(pk *btcec.PublicKey) []byte {
tree, _ := input.NewRemoteCommitScriptTree(pk)
return tree.SettleLeaf.Script
},
expWitnessStack: func(
sig input.Signature) wire.TxWitness {
return [][]byte{sig.Serialize()}
},
createSig: func(priv *btcec.PrivateKey,
digest []byte) input.Signature {
sig, _ := schnorr.Sign(priv, digest)
return sig
},
},
}
for _, test := range tests {
@ -252,8 +330,8 @@ func TestJusticeKitRemoteWitnessConstruction(t *testing.T) {
}
}
func testJusticeKitRemoteWitnessConstruction(
t *testing.T, test remoteWitnessTest) {
func testJusticeKitRemoteWitnessConstruction(t *testing.T,
test remoteWitnessTest) {
// Generate the to-remote pubkey.
toRemotePrivKey, err := btcec.NewPrivateKey()
@ -268,7 +346,7 @@ func testJusticeKitRemoteWitnessConstruction(
// Sign a message using the to-remote private key. The exact message
// doesn't matter as we won't be validating the signature's validity.
digest := bytes.Repeat([]byte("a"), 32)
rawToRemoteSig := ecdsa.Sign(toRemotePrivKey, digest)
rawToRemoteSig := test.createSig(toRemotePrivKey, digest)
// Convert the DER-encoded signature into a fixed-size sig.
commitToRemoteSig, err := lnwire.NewSigFromSignature(rawToRemoteSig)
@ -298,24 +376,122 @@ func testJusticeKitRemoteWitnessConstruction(
expToRemoteScript := test.expWitnessScript(toRemotePrivKey.PubKey())
require.Equal(t, expToRemoteScript, witness[1])
// Compute the expected first element, by appending a sighash all byte
// to our raw DER-encoded signature.
rawToRemoteSigWithSigHash := append(
rawToRemoteSig.Serialize(), byte(txscript.SigHashAll),
)
// Assert that the expected witness stack is returned.
expWitnessStack := wire.TxWitness{
rawToRemoteSigWithSigHash,
}
// Compute the expected signature.
expWitnessStack := test.expWitnessStack(rawToRemoteSig)
require.Equal(t, expWitnessStack, witness[:1])
}
type localWitnessTest struct {
name string
blobType Type
expWitnessScript func(delay, rev *btcec.PublicKey) []byte
expWitnessStack func(sig input.Signature) wire.TxWitness
witnessScriptIndex int
createSig func(*btcec.PrivateKey, []byte) input.Signature
}
// TestJusticeKitToLocalWitnessConstruction tests that a JusticeKit returns the
// proper to-local witness script and to-local witness stack for spending the
// revocation path.
func TestJusticeKitToLocalWitnessConstruction(t *testing.T) {
csvDelay := uint32(144)
tests := []localWitnessTest{
{
name: "legacy commitment",
blobType: TypeAltruistCommit,
expWitnessScript: func(delay,
rev *btcec.PublicKey) []byte {
script, _ := input.CommitScriptToSelf(
csvDelay, delay, rev,
)
return script
},
expWitnessStack: func(
sig input.Signature) wire.TxWitness {
sigBytes := append(
sig.Serialize(),
byte(txscript.SigHashAll),
)
return [][]byte{sigBytes, {1}}
},
witnessScriptIndex: 2,
createSig: func(priv *btcec.PrivateKey,
digest []byte) input.Signature {
return ecdsa.Sign(priv, digest)
},
},
{
name: "anchor commitment",
blobType: TypeAltruistAnchorCommit,
expWitnessScript: func(delay,
rev *btcec.PublicKey) []byte {
script, _ := input.CommitScriptToSelf(
csvDelay, delay, rev,
)
return script
},
witnessScriptIndex: 2,
expWitnessStack: func(
sig input.Signature) wire.TxWitness {
sigBytes := append(
sig.Serialize(),
byte(txscript.SigHashAll),
)
return [][]byte{sigBytes, {1}}
},
createSig: func(priv *btcec.PrivateKey,
digest []byte) input.Signature {
return ecdsa.Sign(priv, digest)
},
},
{
name: "taproot commitment",
blobType: TypeAltruistTaprootCommit,
expWitnessScript: func(delay,
rev *btcec.PublicKey) []byte {
script, _ := input.NewLocalCommitScriptTree(
csvDelay, delay, rev,
)
return script.RevocationLeaf.Script
},
witnessScriptIndex: 1,
expWitnessStack: func(
sig input.Signature) wire.TxWitness {
return [][]byte{sig.Serialize()}
},
createSig: func(priv *btcec.PrivateKey,
digest []byte) input.Signature {
sig, _ := schnorr.Sign(priv, digest)
return sig
},
},
}
for _, test := range tests {
test := test
t.Run(test.name, func(t *testing.T) {
t.Parallel()
testJusticeKitToLocalWitnessConstruction(t, test)
})
}
}
func testJusticeKitToLocalWitnessConstruction(t *testing.T,
test localWitnessTest) {
// Generate the revocation and delay private keys.
revPrivKey, err := btcec.NewPrivateKey()
@ -327,13 +503,13 @@ func TestJusticeKitToLocalWitnessConstruction(t *testing.T) {
// Sign a message using the revocation private key. The exact message
// doesn't matter as we won't be validating the signature's validity.
digest := bytes.Repeat([]byte("a"), 32)
rawRevSig := ecdsa.Sign(revPrivKey, digest)
rawRevSig := test.createSig(revPrivKey, digest)
// Convert the DER-encoded signature into a fixed-size sig.
commitToLocalSig, err := lnwire.NewSigFromSignature(rawRevSig)
require.NoError(t, err)
commitType, err := TypeAltruistCommit.CommitmentType(nil)
commitType, err := test.blobType.CommitmentType(nil)
require.NoError(t, err)
breachInfo := &lnwallet.BreachRetribution{
@ -350,28 +526,18 @@ func TestJusticeKitToLocalWitnessConstruction(t *testing.T) {
// Compute the expected to-local script, which is a function of the CSV
// delay, revocation pubkey and delay pubkey.
expToLocalScript, err := input.CommitScriptToSelf(
csvDelay, delayPrivKey.PubKey(), revPrivKey.PubKey(),
expToLocalScript := test.expWitnessScript(
delayPrivKey.PubKey(), revPrivKey.PubKey(),
)
require.NoError(t, err)
// Compute the to-local script that is returned by the justice kit.
_, witness, err := justiceKit.ToLocalOutputSpendInfo()
require.NoError(t, err)
// Assert that the expected to-local script matches the actual script.
require.Equal(t, expToLocalScript, witness[2])
require.Equal(t, expToLocalScript, witness[test.witnessScriptIndex])
// Compute the expected signature in the bottom element of the stack, by
// appending a sighash all flag to the raw DER signature.
rawRevSigWithSigHash := append(
rawRevSig.Serialize(), byte(txscript.SigHashAll),
)
// Finally, validate against our expected witness stack.
expWitnessStack := wire.TxWitness{
rawRevSigWithSigHash,
{1},
}
require.Equal(t, expWitnessStack, witness[:2])
// Finally, validate the witness.
expWitnessStack := test.expWitnessStack(rawRevSig)
require.Equal(t, expWitnessStack, witness[:test.witnessScriptIndex])
}

View file

@ -26,6 +26,10 @@ const (
// channel, and therefore must expect a P2WSH-style to-remote output if
// one exists.
FlagAnchorChannel Flag = 1 << 2
// FlagTaprootChannel signals that this blob is meant to spend a
// taproot channel and therefore must expect P2TR outputs.
FlagTaprootChannel Flag = 1 << 3
)
// Type returns a Type consisting solely of this flag enabled.
@ -42,6 +46,8 @@ func (f Flag) String() string {
return "FlagCommitOutputs"
case FlagAnchorChannel:
return "FlagAnchorChannel"
case FlagTaprootChannel:
return "FlagTaprootChannel"
default:
return "FlagUnknown"
}
@ -67,8 +73,26 @@ const (
// TypeRewardCommit sweeps only commitment outputs to a sweep address
// controlled by the user, and pays a negotiated reward to the tower.
TypeRewardCommit = Type(FlagCommitOutputs | FlagReward)
// TypeAltruistTaprootCommit sweeps only the commitment outputs from a
// taproot channel commitment to a sweep address controlled by the user,
// and does not give the tower a reward.
TypeAltruistTaprootCommit = Type(FlagCommitOutputs | FlagTaprootChannel)
)
// TypeFromChannel returns the appropriate blob Type for the given channel
// type.
func TypeFromChannel(chanType channeldb.ChannelType) Type {
switch {
case chanType.IsTaproot():
return TypeAltruistTaprootCommit
case chanType.HasAnchors():
return TypeAltruistAnchorCommit
default:
return TypeAltruistCommit
}
}
// Identifier returns a unique, stable string identifier for the blob Type.
func (t Type) Identifier() (string, error) {
switch t {
@ -78,6 +102,8 @@ func (t Type) Identifier() (string, error) {
return "anchor", nil
case TypeRewardCommit:
return "reward", nil
case TypeAltruistTaprootCommit:
return "taproot", nil
default:
return "", fmt.Errorf("unknown blob type: %v", t)
}
@ -89,6 +115,9 @@ func (t Type) CommitmentType(chanType *channeldb.ChannelType) (CommitmentType,
error) {
switch {
case t.Has(FlagTaprootChannel):
return TaprootCommitment, nil
case t.Has(FlagAnchorChannel):
return AnchorCommitment, nil
@ -124,14 +153,20 @@ func (t Type) IsAnchorChannel() bool {
return t.Has(FlagAnchorChannel)
}
// knownFlags maps the supported flags to their name.
var knownFlags = map[Flag]struct{}{
FlagReward: {},
FlagCommitOutputs: {},
FlagAnchorChannel: {},
// IsTaprootChannel returns true if the blob type is for a taproot channel.
func (t Type) IsTaprootChannel() bool {
return t.Has(FlagTaprootChannel)
}
// String returns a human readable description of a Type.
// knownFlags maps the supported flags to their name.
var knownFlags = map[Flag]struct{}{
FlagReward: {},
FlagCommitOutputs: {},
FlagAnchorChannel: {},
FlagTaprootChannel: {},
}
// String returns a human-readable description of a Type.
func (t Type) String() string {
var (
hrPieces []string
@ -175,9 +210,10 @@ func (t Type) String() string {
// supportedTypes is the set of all configurations known to be supported by the
// package.
var supportedTypes = map[Type]struct{}{
TypeAltruistCommit: {},
TypeRewardCommit: {},
TypeAltruistAnchorCommit: {},
TypeAltruistCommit: {},
TypeRewardCommit: {},
TypeAltruistAnchorCommit: {},
TypeAltruistTaprootCommit: {},
}
// IsSupportedType returns true if the given type is supported by the package.

View file

@ -16,19 +16,28 @@ type typeStringTest struct {
var typeStringTests = []typeStringTest{
{
name: "commit no-reward",
typ: blob.TypeAltruistCommit,
expStr: "[No-FlagAnchorChannel|FlagCommitOutputs|No-FlagReward]",
name: "commit no-reward",
typ: blob.TypeAltruistCommit,
expStr: "[No-FlagTaprootChannel|" +
"No-FlagAnchorChannel|" +
"FlagCommitOutputs|" +
"No-FlagReward]",
},
{
name: "commit reward",
typ: blob.TypeRewardCommit,
expStr: "[No-FlagAnchorChannel|FlagCommitOutputs|FlagReward]",
name: "commit reward",
typ: blob.TypeRewardCommit,
expStr: "[No-FlagTaprootChannel|" +
"No-FlagAnchorChannel|" +
"FlagCommitOutputs|" +
"FlagReward]",
},
{
name: "unknown flag",
typ: unknownFlag.Type(),
expStr: "0000000000010000[No-FlagAnchorChannel|No-FlagCommitOutputs|No-FlagReward]",
name: "unknown flag",
typ: unknownFlag.Type(),
expStr: "0000000000010000[No-FlagTaprootChannel|" +
"No-FlagAnchorChannel|" +
"No-FlagCommitOutputs|" +
"No-FlagReward]",
},
}

View file

@ -176,6 +176,8 @@ func (p *JusticeDescriptor) assembleJusticeTxn(txWeight int64,
return nil, fmt.Errorf("error creating previous output "+
"fetcher: %v", err)
}
hashes := txscript.NewTxSigHashes(justiceTxn, prevOutFetcher)
for _, inp := range inputs {
// Lookup the input's new post-sort position.
i := inputIndex[inp.outPoint]
@ -186,7 +188,7 @@ func (p *JusticeDescriptor) assembleJusticeTxn(txWeight int64,
vm, err := txscript.NewEngine(
inp.txOut.PkScript, justiceTxn, i,
txscript.StandardVerifyFlags,
nil, nil, inp.txOut.Value, prevOutFetcher,
nil, hashes, inp.txOut.Value, prevOutFetcher,
)
if err != nil {
return nil, err

View file

@ -10,6 +10,7 @@ import (
"github.com/btcsuite/btcd/btcutil/txsort"
"github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcd/wire"
secp "github.com/decred/dcrd/dcrec/secp256k1/v4"
"github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/keychain"
"github.com/lightningnetwork/lnd/lnwallet"
@ -53,6 +54,8 @@ var (
altruistCommitType = blob.FlagCommitOutputs.Type()
altruistAnchorCommitType = blob.TypeAltruistAnchorCommit
altruisticTaprootCommitType = blob.TypeAltruistTaprootCommit
)
// TestJusticeDescriptor asserts that a JusticeDescriptor is able to produce the
@ -74,6 +77,10 @@ func TestJusticeDescriptor(t *testing.T) {
name: "altruist anchor commit type",
blobType: altruistAnchorCommitType,
},
{
name: "altruist taproot commit type",
blobType: altruisticTaprootCommitType,
},
}
for _, test := range tests {
@ -85,6 +92,7 @@ func TestJusticeDescriptor(t *testing.T) {
func testJusticeDescriptor(t *testing.T, blobType blob.Type) {
isAnchorChannel := blobType.IsAnchorChannel()
isTaprootChannel := blobType.IsTaprootChannel()
const (
localAmount = btcutil.Amount(100000)
@ -108,15 +116,34 @@ func testJusticeDescriptor(t *testing.T, blobType blob.Type) {
toRemoteKeyLoc = signer.AddPrivKey(toRemoteSK)
)
// Construct the to-local witness script.
toLocalScript, err := input.CommitScriptToSelf(
csvDelay, toLocalPK, revPK,
var (
toLocalScript, toLocalScriptHash []byte
toLocalCommitTree *input.CommitScriptTree
)
require.NoError(t, err)
// Compute the to-local witness script hash.
toLocalScriptHash, err := input.WitnessScriptHash(toLocalScript)
require.NoError(t, err)
if isTaprootChannel {
toLocalCommitTree, err = input.NewLocalCommitScriptTree(
csvDelay, toLocalPK, revPK,
)
require.NoError(t, err)
toLocalScript = toLocalCommitTree.RevocationLeaf.Script
toLocalScriptHash, err = input.PayToTaprootScript(
toLocalCommitTree.TaprootKey,
)
require.NoError(t, err)
} else {
// Construct the to-local witness script.
toLocalScript, err = input.CommitScriptToSelf(
csvDelay, toLocalPK, revPK,
)
require.NoError(t, err)
// Compute the to-local witness script hash.
toLocalScriptHash, err = input.WitnessScriptHash(toLocalScript)
require.NoError(t, err)
}
// Compute the to-remote redeem script, witness script hash, and
// sequence numbers.
@ -140,8 +167,37 @@ func testJusticeDescriptor(t *testing.T, blobType blob.Type) {
toRemoteRedeemScript []byte
toRemoteScriptHash []byte
toRemoteSigningScript []byte
toRemoteCtrlBlock []byte
)
if isAnchorChannel {
switch {
case isTaprootChannel:
toRemoteSequence = 1
commitScriptTree, err := input.NewRemoteCommitScriptTree(
toRemotePK,
)
require.NoError(t, err)
toRemoteSigningScript = commitScriptTree.SettleLeaf.Script
toRemoteScriptHash, err = input.PayToTaprootScript(
commitScriptTree.TaprootKey,
)
require.NoError(t, err)
tree := commitScriptTree.TapscriptTree
settleTapleafHash := commitScriptTree.SettleLeaf.TapHash()
settleIdx := tree.LeafProofIndex[settleTapleafHash]
settleMerkleProof := tree.LeafMerkleProofs[settleIdx]
settleControlBlock := settleMerkleProof.ToControlBlock(
&input.TaprootNUMSKey,
)
ctrlBytes, err := settleControlBlock.ToBytes()
require.NoError(t, err)
toRemoteCtrlBlock = ctrlBytes
case isAnchorChannel:
toRemoteSequence = 1
toRemoteRedeemScript, err = input.CommitScriptToRemoteConfirmed(
toRemotePK,
@ -155,7 +211,8 @@ func testJusticeDescriptor(t *testing.T, blobType blob.Type) {
// As it should be.
toRemoteSigningScript = toRemoteRedeemScript
} else {
default:
toRemoteRedeemScript = toRemotePK.SerializeCompressed()
toRemoteScriptHash, err = input.CommitScriptUnencumbered(
toRemotePK,
@ -269,31 +326,82 @@ func testJusticeDescriptor(t *testing.T, blobType blob.Type) {
justiceTxn.TxOut = outputs
txsort.InPlaceSort(justiceTxn)
hashCache := input.NewTxSigHashesV0Only(justiceTxn)
var (
toLocalSignDesc *input.SignDescriptor
toRemoteSignDesc *input.SignDescriptor
)
// Create the sign descriptor used to sign for the to-local input.
toLocalSignDesc := &input.SignDescriptor{
KeyDesc: keychain.KeyDescriptor{
KeyLocator: revKeyLoc,
},
WitnessScript: toLocalScript,
Output: breachTxn.TxOut[0],
SigHashes: hashCache,
InputIndex: 0,
HashType: txscript.SigHashAll,
}
if isTaprootChannel {
prevOuts := map[wire.OutPoint]*wire.TxOut{
{
Hash: breachTxID,
Index: 0,
}: breachTxn.TxOut[0],
{
Hash: breachTxID,
Index: 1,
}: breachTxn.TxOut[1],
}
prevOutputFetcher := txscript.NewMultiPrevOutFetcher(prevOuts)
hashCache := txscript.NewTxSigHashes(
justiceTxn, prevOutputFetcher,
)
// Create the sign descriptor used to sign for the to-remote input.
toRemoteSignDesc := &input.SignDescriptor{
KeyDesc: keychain.KeyDescriptor{
KeyLocator: toRemoteKeyLoc,
PubKey: toRemotePK,
},
WitnessScript: toRemoteSigningScript,
Output: breachTxn.TxOut[1],
SigHashes: hashCache,
InputIndex: 1,
HashType: txscript.SigHashAll,
toLocalSignDesc = &input.SignDescriptor{
KeyDesc: keychain.KeyDescriptor{
KeyLocator: revKeyLoc,
},
WitnessScript: toLocalScript,
Output: breachTxn.TxOut[0],
SigHashes: hashCache,
PrevOutputFetcher: prevOutputFetcher,
InputIndex: 0,
HashType: txscript.SigHashDefault,
SignMethod: input.TaprootScriptSpendSignMethod,
}
toRemoteSignDesc = &input.SignDescriptor{
KeyDesc: keychain.KeyDescriptor{
KeyLocator: toRemoteKeyLoc,
PubKey: toRemotePK,
},
WitnessScript: toRemoteSigningScript,
Output: breachTxn.TxOut[1],
PrevOutputFetcher: prevOutputFetcher,
SigHashes: hashCache,
InputIndex: 1,
HashType: txscript.SigHashDefault,
SignMethod: input.TaprootScriptSpendSignMethod,
}
} else {
hashCache := input.NewTxSigHashesV0Only(justiceTxn)
// Create the sign descriptor used to sign for the to-local
// input.
toLocalSignDesc = &input.SignDescriptor{
KeyDesc: keychain.KeyDescriptor{
KeyLocator: revKeyLoc,
},
WitnessScript: toLocalScript,
Output: breachTxn.TxOut[0],
SigHashes: hashCache,
InputIndex: 0,
HashType: txscript.SigHashAll,
}
// Create the sign descriptor used to sign for the to-remote
// input.
toRemoteSignDesc = &input.SignDescriptor{
KeyDesc: keychain.KeyDescriptor{
KeyLocator: toRemoteKeyLoc,
PubKey: toRemotePK,
},
WitnessScript: toRemoteSigningScript,
Output: breachTxn.TxOut[1],
SigHashes: hashCache,
InputIndex: 1,
HashType: txscript.SigHashAll,
}
}
// Verify that our test justice transaction is sane.
@ -301,21 +409,19 @@ func testJusticeDescriptor(t *testing.T, blobType blob.Type) {
err = blockchain.CheckTransactionSanity(btx)
require.Nil(t, err)
// Compute a DER-encoded signature for the to-local input.
// Compute a signature for the to-local input.
toLocalSigRaw, err := signer.SignOutputRaw(justiceTxn, toLocalSignDesc)
require.Nil(t, err)
// Compute the witness for the to-remote input. The first element is a
// DER-encoded signature under the to-remote pubkey. The sighash flag is
// also present, so we trim it.
// Compute the witness for the to-remote input.
toRemoteSigRaw, err := signer.SignOutputRaw(justiceTxn, toRemoteSignDesc)
require.Nil(t, err)
// Convert the DER to-local sig into a fixed-size signature.
// Convert the to-local sig into a fixed-size signature.
toLocalSig, err := lnwire.NewSigFromSignature(toLocalSigRaw)
require.Nil(t, err)
// Convert the DER to-remote sig into a fixed-size signature.
// Convert the to-remote sig into a fixed-size signature.
toRemoteSig, err := lnwire.NewSigFromSignature(toRemoteSigRaw)
require.Nil(t, err)
@ -342,7 +448,7 @@ func testJusticeDescriptor(t *testing.T, blobType blob.Type) {
// Exact retribution on the offender. If no error is returned, we expect
// the justice transaction to be published via the channel.
err = punisher.Punish(justiceDesc, nil)
require.Nil(t, err)
require.NoError(t, err)
// Retrieve the published justice transaction.
var wtJusticeTxn *wire.MsgTx
@ -352,18 +458,59 @@ func testJusticeDescriptor(t *testing.T, blobType blob.Type) {
t.Fatalf("punisher did not publish justice txn")
}
// Construct the test's to-local witness.
justiceTxn.TxIn[0].Witness = make([][]byte, 3)
justiceTxn.TxIn[0].Witness[0] = append(toLocalSigRaw.Serialize(),
byte(txscript.SigHashAll))
justiceTxn.TxIn[0].Witness[1] = []byte{1}
justiceTxn.TxIn[0].Witness[2] = toLocalScript
if isTaprootChannel {
revokeLeaf := txscript.NewBaseTapLeaf(toLocalScript)
outputKey := txscript.ComputeTaprootOutputKey(
&input.TaprootNUMSKey, toLocalCommitTree.TapscriptRoot,
)
// Construct the test's to-remote witness.
justiceTxn.TxIn[1].Witness = make([][]byte, 2)
justiceTxn.TxIn[1].Witness[0] = append(toRemoteSigRaw.Serialize(),
byte(txscript.SigHashAll))
justiceTxn.TxIn[1].Witness[1] = toRemoteRedeemScript
var outputKeyYIsOdd bool
if outputKey.SerializeCompressed()[0] ==
secp.PubKeyFormatCompressedOdd {
outputKeyYIsOdd = true
}
delayScriptHash := toLocalCommitTree.SettleLeaf.TapHash()
ctrlBlock := txscript.ControlBlock{
InternalKey: &input.TaprootNUMSKey,
OutputKeyYIsOdd: outputKeyYIsOdd,
LeafVersion: revokeLeaf.LeafVersion,
InclusionProof: delayScriptHash[:],
}
ctrlBytes, err := ctrlBlock.ToBytes()
require.NoError(t, err)
justiceTxn.TxIn[0].Witness = make([][]byte, 3)
justiceTxn.TxIn[0].Witness[0] = toLocalSigRaw.Serialize()
justiceTxn.TxIn[0].Witness[1] = toLocalScript
justiceTxn.TxIn[0].Witness[2] = ctrlBytes
// Construct the test's to-remote witness.
justiceTxn.TxIn[1].Witness = make([][]byte, 3)
justiceTxn.TxIn[1].Witness[0] = toRemoteSigRaw.Serialize()
justiceTxn.TxIn[1].Witness[1] = toRemoteSigningScript
justiceTxn.TxIn[1].Witness[2] = toRemoteCtrlBlock
} else {
// Construct the test's to-local witness.
justiceTxn.TxIn[0].Witness = make([][]byte, 3)
justiceTxn.TxIn[0].Witness[0] = append(
toLocalSigRaw.Serialize(),
byte(txscript.SigHashAll),
)
justiceTxn.TxIn[0].Witness[1] = []byte{1}
justiceTxn.TxIn[0].Witness[2] = toLocalScript
// Construct the test's to-remote witness.
justiceTxn.TxIn[1].Witness = make([][]byte, 2)
justiceTxn.TxIn[1].Witness[0] = append(
toRemoteSigRaw.Serialize(),
byte(txscript.SigHashAll),
)
justiceTxn.TxIn[1].Witness[1] = toRemoteRedeemScript
}
// Assert that the watchtower derives the same justice txn.
require.Equal(t, justiceTxn, wtJusticeTxn)

View file

@ -213,12 +213,6 @@ func (t *backupTask) bindSession(session *wtdb.ClientSessionBody,
}
}
if chanType.HasAnchors() != session.Policy.IsAnchorChannel() {
log.Criticalf("Invalid task (has_anchors=%t) for session "+
"(has_anchors=%t)", chanType.HasAnchors(),
session.Policy.IsAnchorChannel())
}
// Now, compute the output values depending on whether FlagReward is set
// in the current session's policy.
outputs, err := session.Policy.ComputeJusticeTxOuts(
@ -334,6 +328,7 @@ func (t *backupTask) craftSessionPayload(
switch inp.WitnessType() {
case toLocalWitnessType:
justiceKit.AddToLocalSig(signature)
case toRemoteWitnessType:
justiceKit.AddToRemoteSig(signature)
default:

View file

@ -85,9 +85,11 @@ func genTaskTest(
bindErr error,
chanType channeldb.ChannelType) backupTaskTest {
// Set the anchor flag in the blob type if the session needs to support
// anchor channels.
if chanType.HasAnchors() {
// Set the anchor or taproot flag in the blob type if the session needs
// to support anchor or taproot channels.
if chanType.IsTaproot() {
blobType |= blob.Type(blob.FlagTaprootChannel)
} else if chanType.HasAnchors() {
blobType |= blob.Type(blob.FlagAnchorChannel)
}
@ -129,30 +131,112 @@ func genTaskTest(
// to that output as local, though relative to their commitment, it is
// paying to-the-remote party (which is us).
if toLocalAmt > 0 {
toLocalSignDesc := &input.SignDescriptor{
KeyDesc: keychain.KeyDescriptor{
KeyLocator: revKeyLoc,
PubKey: revPK,
},
Output: &wire.TxOut{
Value: toLocalAmt,
},
HashType: txscript.SigHashAll,
var toLocalSignDesc *input.SignDescriptor
if chanType.IsTaproot() {
scriptTree, _ := input.NewLocalCommitScriptTree(
csvDelay, toLocalPK, revPK,
)
pkScript, _ := input.PayToTaprootScript(
scriptTree.TaprootKey,
)
revokeTapleafHash := txscript.NewBaseTapLeaf(
scriptTree.RevocationLeaf.Script,
).TapHash()
tapTree := scriptTree.TapscriptTree
revokeIdx := tapTree.LeafProofIndex[revokeTapleafHash]
revokeMerkleProof := tapTree.LeafMerkleProofs[revokeIdx]
revokeControlBlock := revokeMerkleProof.ToControlBlock(
&input.TaprootNUMSKey,
)
ctrlBytes, _ := revokeControlBlock.ToBytes()
toLocalSignDesc = &input.SignDescriptor{
KeyDesc: keychain.KeyDescriptor{
KeyLocator: revKeyLoc,
PubKey: revPK,
},
Output: &wire.TxOut{
Value: toLocalAmt,
PkScript: pkScript,
},
WitnessScript: scriptTree.RevocationLeaf.Script,
SignMethod: input.TaprootScriptSpendSignMethod, //nolint:lll
HashType: txscript.SigHashDefault,
ControlBlock: ctrlBytes,
}
} else {
toLocalSignDesc = &input.SignDescriptor{
KeyDesc: keychain.KeyDescriptor{
KeyLocator: revKeyLoc,
PubKey: revPK,
},
Output: &wire.TxOut{
Value: toLocalAmt,
},
HashType: txscript.SigHashAll,
}
}
breachInfo.RemoteOutputSignDesc = toLocalSignDesc
breachTxn.AddTxOut(toLocalSignDesc.Output)
}
if toRemoteAmt > 0 {
toRemoteSignDesc := &input.SignDescriptor{
KeyDesc: keychain.KeyDescriptor{
KeyLocator: toRemoteKeyLoc,
PubKey: toRemotePK,
},
Output: &wire.TxOut{
Value: toRemoteAmt,
},
HashType: txscript.SigHashAll,
var toRemoteSignDesc *input.SignDescriptor
if chanType.IsTaproot() {
scriptTree, _ := input.NewRemoteCommitScriptTree(
toRemotePK,
)
pkScript, _ := input.PayToTaprootScript(
scriptTree.TaprootKey,
)
revokeTapleafHash := txscript.NewBaseTapLeaf(
scriptTree.SettleLeaf.Script,
).TapHash()
tapTree := scriptTree.TapscriptTree
revokeIdx := tapTree.LeafProofIndex[revokeTapleafHash]
revokeMerkleProof := tapTree.LeafMerkleProofs[revokeIdx]
revokeControlBlock := revokeMerkleProof.ToControlBlock(
&input.TaprootNUMSKey,
)
ctrlBytes, _ := revokeControlBlock.ToBytes()
toRemoteSignDesc = &input.SignDescriptor{
KeyDesc: keychain.KeyDescriptor{
KeyLocator: toRemoteKeyLoc,
PubKey: toRemotePK,
},
WitnessScript: scriptTree.SettleLeaf.Script,
SignMethod: input.TaprootScriptSpendSignMethod, //nolint:lll
Output: &wire.TxOut{
Value: toRemoteAmt,
PkScript: pkScript,
},
HashType: txscript.SigHashDefault,
InputIndex: 1,
ControlBlock: ctrlBytes,
}
} else {
toRemoteSignDesc = &input.SignDescriptor{
KeyDesc: keychain.KeyDescriptor{
KeyLocator: toRemoteKeyLoc,
PubKey: toRemotePK,
},
Output: &wire.TxOut{
Value: toRemoteAmt,
},
HashType: txscript.SigHashAll,
}
}
breachInfo.LocalOutputSignDesc = toRemoteSignDesc
breachTxn.AddTxOut(toRemoteSignDesc.Output)
}
@ -248,6 +332,7 @@ func TestBackupTask(t *testing.T) {
channeldb.SingleFunderBit,
channeldb.SingleFunderTweaklessBit,
channeldb.AnchorOutputsBit,
channeldb.SimpleTaprootFeatureBit,
}
var backupTaskTests []backupTaskTest
@ -272,7 +357,16 @@ func TestBackupTask(t *testing.T) {
sweepFeeRateNoRewardRemoteDust chainfee.SatPerKWeight = 227500
sweepFeeRateRewardRemoteDust chainfee.SatPerKWeight = 175350
)
if chanType.HasAnchors() {
if chanType.IsTaproot() {
expSweepCommitNoRewardBoth = 299165
expSweepCommitNoRewardLocal = 199468
expSweepCommitNoRewardRemote = 99531
sweepFeeRateNoRewardRemoteDust = 213200
expSweepCommitRewardBoth = 295993
expSweepCommitRewardLocal = 197296
expSweepCommitRewardRemote = 98359
sweepFeeRateRewardRemoteDust = 167000
} else if chanType.HasAnchors() {
expSweepCommitNoRewardBoth = 299236
expSweepCommitNoRewardLocal = 199513
expSweepCommitNoRewardRemote = 99557

View file

@ -76,12 +76,12 @@ var (
waitTime = 15 * time.Second
defaultTxPolicy = wtpolicy.TxPolicy{
BlobType: blob.TypeAltruistCommit,
BlobType: blob.TypeAltruistTaprootCommit,
SweepFeeRate: wtpolicy.DefaultSweepFeeRate,
}
highSweepRateTxPolicy = wtpolicy.TxPolicy{
BlobType: blob.TypeAltruistCommit,
BlobType: blob.TypeAltruistTaprootCommit,
SweepFeeRate: 1000000, // The high sweep fee creates dust.
}
)
@ -229,17 +229,25 @@ func (c *mockChannel) createRemoteCommitTx(t *testing.T) {
t.Helper()
// Construct the to-local witness script.
toLocalScript, err := input.CommitScriptToSelf(
toLocalScriptTree, err := input.NewLocalCommitScriptTree(
c.csvDelay, c.toLocalPK, c.revPK,
)
require.NoError(t, err, "unable to create to-local script")
// Construct the to-remote witness script.
toRemoteScriptTree, err := input.NewRemoteCommitScriptTree(c.toRemotePK)
require.NoError(t, err, "unable to create to-remote script")
// Compute the to-local witness script hash.
toLocalScriptHash, err := input.WitnessScriptHash(toLocalScript)
toLocalScriptHash, err := input.PayToTaprootScript(
toLocalScriptTree.TaprootKey,
)
require.NoError(t, err, "unable to create to-local witness script hash")
// Compute the to-remote witness script hash.
toRemoteScriptHash, err := input.CommitScriptUnencumbered(c.toRemotePK)
toRemoteScriptHash, err := input.PayToTaprootScript(
toRemoteScriptTree.TaprootKey,
)
require.NoError(t, err, "unable to create to-remote script")
// Construct the remote commitment txn, containing the to-local and
@ -264,6 +272,19 @@ func (c *mockChannel) createRemoteCommitTx(t *testing.T) {
PkScript: toLocalScriptHash,
})
revokeTapleafHash := txscript.NewBaseTapLeaf(
toLocalScriptTree.RevocationLeaf.Script,
).TapHash()
tapTree := toLocalScriptTree.TapscriptTree
revokeIdx := tapTree.LeafProofIndex[revokeTapleafHash]
revokeMerkleProof := tapTree.LeafMerkleProofs[revokeIdx]
revokeControlBlock := revokeMerkleProof.ToControlBlock(
&input.TaprootNUMSKey,
)
ctrlBytes, err := revokeControlBlock.ToBytes()
require.NoError(t, err)
// Create the sign descriptor used to sign for the to-local
// input.
toLocalSignDesc = &input.SignDescriptor{
@ -271,9 +292,11 @@ func (c *mockChannel) createRemoteCommitTx(t *testing.T) {
KeyLocator: c.revKeyLoc,
PubKey: c.revPK,
},
WitnessScript: toLocalScript,
WitnessScript: toLocalScriptTree.RevocationLeaf.Script,
Output: commitTxn.TxOut[outputIndex],
HashType: txscript.SigHashAll,
HashType: txscript.SigHashDefault,
SignMethod: input.TaprootScriptSpendSignMethod,
ControlBlock: ctrlBytes,
}
outputIndex++
}
@ -283,6 +306,18 @@ func (c *mockChannel) createRemoteCommitTx(t *testing.T) {
PkScript: toRemoteScriptHash,
})
toRemoteTapleafHash := txscript.NewBaseTapLeaf(
toRemoteScriptTree.SettleLeaf.Script,
).TapHash()
tapTree := toRemoteScriptTree.TapscriptTree
remoteIdx := tapTree.LeafProofIndex[toRemoteTapleafHash]
remoteMerkleProof := tapTree.LeafMerkleProofs[remoteIdx]
remoteControlBlock := remoteMerkleProof.ToControlBlock(
&input.TaprootNUMSKey,
)
ctrlBytes, _ := remoteControlBlock.ToBytes()
// Create the sign descriptor used to sign for the to-remote
// input.
toRemoteSignDesc = &input.SignDescriptor{
@ -290,9 +325,11 @@ func (c *mockChannel) createRemoteCommitTx(t *testing.T) {
KeyLocator: c.toRemoteKeyLoc,
PubKey: c.toRemotePK,
},
WitnessScript: toRemoteScriptHash,
WitnessScript: toRemoteScriptTree.SettleLeaf.Script,
Output: commitTxn.TxOut[outputIndex],
HashType: txscript.SigHashAll,
HashType: txscript.SigHashDefault,
SignMethod: input.TaprootScriptSpendSignMethod,
ControlBlock: ctrlBytes,
}
outputIndex++
}
@ -516,7 +553,7 @@ func newHarness(t *testing.T, cfg harnessCfg) *testHarness {
_, retribution := h.channelFromID(id).getState(commitHeight)
return retribution, channeldb.SingleFunderBit, nil
return retribution, channeldb.SimpleTaprootFeatureBit, nil
}
if !cfg.noServerStart {
@ -664,7 +701,9 @@ func (h *testHarness) registerChannel(id uint64) {
h.t.Helper()
chanID := chanIDFromInt(id)
err := h.clientMgr.RegisterChannel(chanID, channeldb.SingleFunderBit)
err := h.clientMgr.RegisterChannel(
chanID, channeldb.SimpleTaprootFeatureBit,
)
require.NoError(h.t, err)
}
@ -1404,7 +1443,7 @@ var clientTests = []clientTest{
// Wait for all the updates to be populated in the
// server's database.
h.server.waitForUpdates(hints, 10*time.Second)
h.server.waitForUpdates(hints, waitTime)
},
},
{

View file

@ -523,10 +523,7 @@ func (m *Manager) Policy(blobType blob.Type) (wtpolicy.Policy, error) {
func (m *Manager) RegisterChannel(id lnwire.ChannelID,
chanType channeldb.ChannelType) error {
blobType := blob.TypeAltruistCommit
if chanType.HasAnchors() {
blobType = blob.TypeAltruistAnchorCommit
}
blobType := blob.TypeFromChannel(chanType)
m.clientsMu.Lock()
if _, ok := m.clients[blobType]; !ok {

View file

@ -120,15 +120,8 @@ var _ SessionNegotiator = (*sessionNegotiator)(nil)
// newSessionNegotiator initializes a fresh sessionNegotiator instance.
func newSessionNegotiator(cfg *NegotiatorConfig) *sessionNegotiator {
// Generate the set of features the negotiator will present to the tower
// upon connection. For anchor channels, we'll conditionally signal that
// we require support for anchor channels depending on the requested
// policy.
features := []lnwire.FeatureBit{
wtwire.AltruistSessionsRequired,
}
if cfg.Policy.IsAnchorChannel() {
features = append(features, wtwire.AnchorCommitRequired)
}
// upon connection.
features := cfg.Policy.FeatureBits()
localInit := wtwire.NewInitMessage(
lnwire.NewRawFeatureVector(features...),

View file

@ -2,6 +2,7 @@ package wtmock
import (
"crypto/sha256"
"fmt"
"sync"
"github.com/btcsuite/btcd/btcec/v2"
@ -47,6 +48,69 @@ func (s *MockSigner) SignOutputRaw(tx *wire.MsgTx,
panic("cannot sign w/ unknown key")
}
// In case of a taproot output any signature is always a Schnorr
// signature, based on the new tapscript sighash algorithm.
if txscript.IsPayToTaproot(signDesc.Output.PkScript) {
sigHashes := txscript.NewTxSigHashes(
tx, signDesc.PrevOutputFetcher,
)
// Are we spending a script path or the key path? The API is
// slightly different, so we need to account for that to get the
// raw signature.
var (
rawSig []byte
err error
)
switch signDesc.SignMethod {
case input.TaprootKeySpendBIP0086SignMethod,
input.TaprootKeySpendSignMethod:
// This function tweaks the private key using the tap
// root key supplied as the tweak.
rawSig, err = txscript.RawTxInTaprootSignature(
tx, sigHashes, signDesc.InputIndex,
signDesc.Output.Value, signDesc.Output.PkScript,
signDesc.TapTweak, signDesc.HashType,
privKey,
)
if err != nil {
return nil, err
}
case input.TaprootScriptSpendSignMethod:
leaf := txscript.TapLeaf{
LeafVersion: txscript.BaseLeafVersion,
Script: witnessScript,
}
rawSig, err = txscript.RawTxInTapscriptSignature(
tx, sigHashes, signDesc.InputIndex,
signDesc.Output.Value, signDesc.Output.PkScript,
leaf, signDesc.HashType, privKey,
)
if err != nil {
return nil, err
}
default:
return nil, fmt.Errorf("unknown sign method: %v",
signDesc.SignMethod)
}
// The signature returned above might have a sighash flag
// attached if a non-default type was used. We'll slice this
// off if it exists to ensure we can properly parse the raw
// signature.
sig, err := schnorr.ParseSignature(
rawSig[:schnorr.SignatureSize],
)
if err != nil {
return nil, err
}
return sig, nil
}
sig, err := txscript.RawTxInWitnessSignature(
tx, signDesc.SigHashes, signDesc.InputIndex, amt,
witnessScript, signDesc.HashType, privKey,

View file

@ -8,7 +8,9 @@ import (
"github.com/btcsuite/btcd/wire"
"github.com/lightningnetwork/lnd/lnwallet"
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/watchtower/blob"
"github.com/lightningnetwork/lnd/watchtower/wtwire"
)
const (
@ -120,14 +122,38 @@ func (p Policy) String() string {
p.SweepFeeRate)
}
// FeatureBits returns the watchtower feature bits required for the given
// policy.
func (p *Policy) FeatureBits() []lnwire.FeatureBit {
features := []lnwire.FeatureBit{
wtwire.AltruistSessionsRequired,
}
t := p.TxPolicy.BlobType
switch {
case t.IsTaprootChannel():
features = append(features, wtwire.TaprootCommitRequired)
case t.IsAnchorChannel():
features = append(features, wtwire.AnchorCommitRequired)
}
return features
}
// IsAnchorChannel returns true if the session policy requires anchor channels.
func (p Policy) IsAnchorChannel() bool {
func (p *Policy) IsAnchorChannel() bool {
return p.TxPolicy.BlobType.IsAnchorChannel()
}
// IsTaprootChannel returns true if the session policy requires taproot
// channels.
func (p *Policy) IsTaprootChannel() bool {
return p.TxPolicy.BlobType.IsTaprootChannel()
}
// Validate ensures that the policy satisfies some minimal correctness
// constraints.
func (p Policy) Validate() error {
func (p *Policy) Validate() error {
// RewardBase and RewardRate should not be set if the policy doesn't
// have a reward.
if !p.BlobType.Has(blob.FlagReward) &&

View file

@ -93,20 +93,32 @@ func TestPolicyValidate(t *testing.T) {
}
}
// TestPolicyIsAnchorChannel asserts that the IsAnchorChannel helper properly
// reflects the anchor bit of the policy's blob type.
func TestPolicyIsAnchorChannel(t *testing.T) {
policyNoAnchor := wtpolicy.Policy{
// TestPolicyIsChannelType asserts that the IsAnchorChannel and IsTaprootChannel
// helpers properly reflect the anchor bit of the policy's blob type.
func TestPolicyIsChannelType(t *testing.T) {
t.Parallel()
policyLegacy := wtpolicy.Policy{
TxPolicy: wtpolicy.TxPolicy{
BlobType: blob.TypeAltruistCommit,
},
}
require.Equal(t, false, policyNoAnchor.IsAnchorChannel())
require.False(t, policyLegacy.IsAnchorChannel())
require.False(t, policyLegacy.IsTaprootChannel())
policyAnchor := wtpolicy.Policy{
TxPolicy: wtpolicy.TxPolicy{
BlobType: blob.TypeAltruistAnchorCommit,
},
}
require.Equal(t, true, policyAnchor.IsAnchorChannel())
require.True(t, policyAnchor.IsAnchorChannel())
require.False(t, policyAnchor.IsTaprootChannel())
policyTaproot := wtpolicy.Policy{
TxPolicy: wtpolicy.TxPolicy{
BlobType: blob.TypeAltruistTaprootCommit,
},
}
require.True(t, policyTaproot.IsTaprootChannel())
require.False(t, policyTaproot.IsAnchorChannel())
}

View file

@ -9,6 +9,8 @@ var FeatureNames = map[lnwire.FeatureBit]string{
AltruistSessionsOptional: "altruist-sessions",
AnchorCommitRequired: "anchor-commit",
AnchorCommitOptional: "anchor-commit",
TaprootCommitRequired: "taproot-commit",
TaprootCommitOptional: "taproot-commit",
}
const (
@ -30,4 +32,13 @@ const (
// AnchorCommitOptional specifies that the advertising tower allows the
// remote party to negotiate sessions for protecting anchor channels.
AnchorCommitOptional lnwire.FeatureBit = 3
// TaprootCommitRequired specifies that the advertising tower requires
// the remote party to negotiate sessions for protecting taproot
// channels.
TaprootCommitRequired lnwire.FeatureBit = 4
// TaprootCommitOptional specifies that the advertising tower allows the
// remote party to negotiate sessions for protecting taproot channels.
TaprootCommitOptional lnwire.FeatureBit = 5
)