mirror of
https://github.com/lightningnetwork/lnd.git
synced 2025-03-13 11:09:23 +01:00
Merge pull request #7733 from ellemouton/taprootTowers
watchtower: support taproot channel commitments
This commit is contained in:
commit
0a29b37be6
27 changed files with 1452 additions and 320 deletions
|
@ -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:
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 (
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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])
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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]",
|
||||
},
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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...),
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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) &&
|
||||
|
|
|
@ -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())
|
||||
}
|
||||
|
|
|
@ -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
|
||||
)
|
||||
|
|
Loading…
Add table
Reference in a new issue