github.com/decred/dcrlnd@v0.7.6/watchtower/wtclient/backup_task_internal_test.go (about) 1 package wtclient 2 3 import ( 4 "bytes" 5 "crypto/rand" 6 "io" 7 "reflect" 8 "testing" 9 10 "github.com/davecgh/go-spew/spew" 11 "github.com/decred/dcrd/chaincfg/v3" 12 "github.com/decred/dcrd/dcrec/secp256k1/v4" 13 "github.com/decred/dcrd/dcrutil/v4" 14 "github.com/decred/dcrd/txscript/v4" 15 "github.com/decred/dcrd/txscript/v4/stdaddr" 16 "github.com/decred/dcrd/wire" 17 "github.com/decred/dcrlnd/channeldb" 18 "github.com/decred/dcrlnd/input" 19 "github.com/decred/dcrlnd/keychain" 20 "github.com/decred/dcrlnd/lnwallet" 21 "github.com/decred/dcrlnd/lnwallet/chainfee" 22 "github.com/decred/dcrlnd/lnwire" 23 "github.com/decred/dcrlnd/watchtower/blob" 24 "github.com/decred/dcrlnd/watchtower/wtdb" 25 "github.com/decred/dcrlnd/watchtower/wtmock" 26 "github.com/decred/dcrlnd/watchtower/wtpolicy" 27 ) 28 29 const csvDelay uint32 = 144 30 31 var ( 32 zeroPK [33]byte 33 zeroSig [64]byte 34 35 revPrivBytes = []byte{ 36 0x8f, 0x4b, 0x51, 0x83, 0xa9, 0x34, 0xbd, 0x5f, 37 0x74, 0x6c, 0x9d, 0x5c, 0xae, 0x88, 0x2d, 0x31, 38 0x06, 0x90, 0xdd, 0x8c, 0x9b, 0x31, 0xbc, 0xd1, 39 0x78, 0x91, 0x88, 0x2a, 0xf9, 0x74, 0xa0, 0xef, 40 } 41 42 toLocalPrivBytes = []byte{ 43 0xde, 0x17, 0xc1, 0x2f, 0xdc, 0x1b, 0xc0, 0xc6, 44 0x59, 0x5d, 0xf9, 0xc1, 0x3e, 0x89, 0xbc, 0x6f, 45 0x01, 0x85, 0x45, 0x76, 0x26, 0xce, 0x9c, 0x55, 46 0x3b, 0xc9, 0xec, 0x3d, 0xd8, 0x8b, 0xac, 0xa8, 47 } 48 49 toRemotePrivBytes = []byte{ 50 0x28, 0x59, 0x6f, 0x36, 0xb8, 0x9f, 0x19, 0x5d, 51 0xcb, 0x07, 0x48, 0x8a, 0xe5, 0x89, 0x71, 0x74, 52 0x70, 0x4c, 0xff, 0x1e, 0x9c, 0x00, 0x93, 0xbe, 53 0xe2, 0x2e, 0x68, 0x08, 0x4c, 0xb4, 0x0f, 0x4f, 54 } 55 ) 56 57 func makeAddrSlice(size int) []byte { 58 if size > 75 { 59 // This is needed because decred requires a valid script in the 60 // pkscript. To simpify tests, we generate a dummy OP_RETURN 61 // with a data push so that the total pkscript size is the 62 // parametrized size. 63 panic("wrong usage") 64 } 65 addr := make([]byte, size) 66 addr[0] = 0x6a // OP_RETURN 67 addr[1] = byte(size - 2) 68 if _, err := io.ReadFull(rand.Reader, addr[2:]); err != nil { 69 panic("cannot make addr") 70 } 71 return addr 72 } 73 74 type backupTaskTest struct { 75 name string 76 chanID lnwire.ChannelID 77 breachInfo *lnwallet.BreachRetribution 78 expToLocalInput input.Input 79 expToRemoteInput input.Input 80 expTotalAmt dcrutil.Amount 81 expSweepAmt int64 82 expRewardAmt int64 83 expRewardScript []byte 84 session *wtdb.ClientSessionBody 85 bindErr error 86 expSweepScript []byte 87 signer input.Signer 88 chanType channeldb.ChannelType 89 } 90 91 func privKeyFromBytes(b []byte) (*secp256k1.PrivateKey, *secp256k1.PublicKey) { 92 p := secp256k1.PrivKeyFromBytes(b) 93 return p, p.PubKey() 94 } 95 96 // genTaskTest creates a instance of a backupTaskTest using the passed 97 // parameters. This method handles generating a breach transaction and its 98 // corresponding BreachInfo, as well as setting the wtpolicy.Policy of the given 99 // session. 100 func genTaskTest( 101 name string, 102 stateNum uint64, 103 toLocalAmt int64, 104 toRemoteAmt int64, 105 blobType blob.Type, 106 sweepFeeRate chainfee.AtomPerKByte, 107 rewardScript []byte, 108 expSweepAmt int64, 109 expRewardAmt int64, 110 bindErr error, 111 chanType channeldb.ChannelType) backupTaskTest { 112 113 // Set the anchor flag in the blob type if the session needs to support 114 // anchor channels. 115 if chanType.HasAnchors() { 116 blobType |= blob.Type(blob.FlagAnchorChannel) 117 } 118 119 // Parse the key pairs for all keys used in the test. 120 revSK, revPK := privKeyFromBytes( 121 revPrivBytes, 122 ) 123 _, toLocalPK := privKeyFromBytes( 124 toLocalPrivBytes, 125 ) 126 toRemoteSK, toRemotePK := privKeyFromBytes( 127 toRemotePrivBytes, 128 ) 129 130 // Create the signer, and add the revocation and to-remote privkeys. 131 signer := wtmock.NewMockSigner() 132 var ( 133 revKeyLoc = signer.AddPrivKey(revSK) 134 toRemoteKeyLoc = signer.AddPrivKey(toRemoteSK) 135 ) 136 137 // First, we'll initialize a new breach transaction and the 138 // corresponding breach retribution. The retribution stores a pointer to 139 // the breach transaction, which we will continue to modify. 140 breachTxn := wire.NewMsgTx() 141 breachTxn.Version = 2 142 breachInfo := &lnwallet.BreachRetribution{ 143 RevokedStateNum: stateNum, 144 BreachTransaction: breachTxn, 145 KeyRing: &lnwallet.CommitmentKeyRing{ 146 RevocationKey: revPK, 147 ToLocalKey: toLocalPK, 148 ToRemoteKey: toRemotePK, 149 }, 150 RemoteDelay: csvDelay, 151 } 152 153 // Add the sign descriptors and outputs corresponding to the to-local 154 // and to-remote outputs, respectively, if either input amount is 155 // non-zero. Note that the naming here seems reversed, but both are 156 // correct. For example, the to-remote output on the remote party's 157 // commitment is an output that pays to us. Hence the retribution refers 158 // to that output as local, though relative to their commitment, it is 159 // paying to-the-remote party (which is us). 160 if toLocalAmt > 0 { 161 toLocalSignDesc := &input.SignDescriptor{ 162 KeyDesc: keychain.KeyDescriptor{ 163 KeyLocator: revKeyLoc, 164 PubKey: revPK, 165 }, 166 Output: &wire.TxOut{ 167 Value: toLocalAmt, 168 }, 169 HashType: txscript.SigHashAll, 170 } 171 breachInfo.RemoteOutputSignDesc = toLocalSignDesc 172 breachTxn.AddTxOut(toLocalSignDesc.Output) 173 } 174 if toRemoteAmt > 0 { 175 toRemoteSignDesc := &input.SignDescriptor{ 176 KeyDesc: keychain.KeyDescriptor{ 177 KeyLocator: toRemoteKeyLoc, 178 PubKey: toRemotePK, 179 }, 180 Output: &wire.TxOut{ 181 Value: toRemoteAmt, 182 }, 183 HashType: txscript.SigHashAll, 184 } 185 breachInfo.LocalOutputSignDesc = toRemoteSignDesc 186 breachTxn.AddTxOut(toRemoteSignDesc.Output) 187 } 188 189 var ( 190 toLocalInput input.Input 191 toRemoteInput input.Input 192 ) 193 194 // Now that the breach transaction has all its outputs, we can compute 195 // its txid and inputs spending from it. We also generate the 196 // input.Inputs that should be derived by the backup task. 197 txid := breachTxn.TxHash() 198 var index uint32 199 if toLocalAmt > 0 { 200 breachInfo.RemoteOutpoint = wire.OutPoint{ 201 Hash: txid, 202 Index: index, 203 } 204 toLocalInput = input.NewBaseInput( 205 &breachInfo.RemoteOutpoint, 206 input.CommitmentRevoke, 207 breachInfo.RemoteOutputSignDesc, 208 0, 209 ) 210 index++ 211 } 212 if toRemoteAmt > 0 { 213 breachInfo.LocalOutpoint = wire.OutPoint{ 214 Hash: txid, 215 Index: index, 216 } 217 218 var witnessType input.WitnessType 219 switch { 220 case chanType.HasAnchors(): 221 witnessType = input.CommitmentToRemoteConfirmed 222 case chanType.IsTweakless(): 223 witnessType = input.CommitSpendNoDelayTweakless 224 default: 225 witnessType = input.CommitmentNoDelay 226 } 227 228 if chanType.HasAnchors() { 229 toRemoteInput = input.NewCsvInput( 230 &breachInfo.LocalOutpoint, 231 witnessType, 232 breachInfo.LocalOutputSignDesc, 233 0, 1, 234 ) 235 } else { 236 toRemoteInput = input.NewBaseInput( 237 &breachInfo.LocalOutpoint, 238 witnessType, 239 breachInfo.LocalOutputSignDesc, 240 0, 241 ) 242 } 243 } 244 245 return backupTaskTest{ 246 name: name, 247 breachInfo: breachInfo, 248 expToLocalInput: toLocalInput, 249 expToRemoteInput: toRemoteInput, 250 expTotalAmt: dcrutil.Amount(toLocalAmt + toRemoteAmt), 251 expSweepAmt: expSweepAmt, 252 expRewardAmt: expRewardAmt, 253 expRewardScript: rewardScript, 254 session: &wtdb.ClientSessionBody{ 255 Policy: wtpolicy.Policy{ 256 TxPolicy: wtpolicy.TxPolicy{ 257 BlobType: blobType, 258 SweepFeeRate: sweepFeeRate, 259 RewardRate: 10000, 260 }, 261 }, 262 RewardPkScript: rewardScript, 263 }, 264 bindErr: bindErr, 265 expSweepScript: makeAddrSlice(22), 266 signer: signer, 267 chanType: chanType, 268 } 269 } 270 271 var ( 272 blobTypeCommitNoReward = blob.FlagCommitOutputs.Type() 273 274 blobTypeCommitReward = (blob.FlagCommitOutputs | blob.FlagReward).Type() 275 276 addr, _ = stdaddr.DecodeAddress( 277 "Tsi6gGYNSMmFwi7JoL5Li39SrERZTTMu6vY", 278 chaincfg.TestNet3Params(), 279 ) 280 281 addrScript, _ = input.PayToAddrScript(addr) 282 ) 283 284 // TestBackupTaskBind tests the initialization and binding of a backupTask to a 285 // ClientSession. After a successful bind, all parameters of the justice 286 // transaction should be solidified, so we assert there correctness. In an 287 // unsuccessful bind, the session-dependent parameters should be unmodified so 288 // that the backup task can be rescheduled if necessary. Finally, we assert 289 // that the backup task is able to encrypt a valid justice kit, and that we can 290 // decrypt it using the breach txid. 291 func TestBackupTask(t *testing.T) { 292 t.Parallel() 293 294 chanTypes := []channeldb.ChannelType{ 295 channeldb.SingleFunderBit, 296 channeldb.SingleFunderTweaklessBit, 297 channeldb.AnchorOutputsBit, 298 } 299 300 var backupTaskTests []backupTaskTest 301 for _, chanType := range chanTypes { 302 // Depending on whether the test is for anchor channels or 303 // legacy (tweaked and non-tweaked) channels, adjust the 304 // expected sweep amount to accommodate. These are different for 305 // several reasons: 306 // - anchor to-remote outputs require a P2WSH sweep rather 307 // than a P2WKH sweep. 308 // - the to-local weight estimate fixes an off-by-one. 309 // In tests related to the dust threshold, the size difference 310 // between the channel types makes it so that the threshold fee 311 // rate is slightly lower (since the transactions are heavier). 312 var ( 313 expSweepCommitNoRewardBoth int64 = 299568 314 expSweepCommitNoRewardLocal int64 = 199734 315 expSweepCommitNoRewardRemote int64 = 99783 316 expSweepCommitRewardBoth int64 = 296532 317 expSweepCommitRewardLocal int64 = 197698 318 expSweepCommitRewardRemote int64 = 98747 319 sweepFeeRateNoRewardRemoteDust chainfee.AtomPerKByte = 455000 320 sweepFeeRateRewardRemoteDust chainfee.AtomPerKByte = 385000 321 ) 322 if chanType.HasAnchors() { 323 expSweepCommitNoRewardBoth = 299564 324 expSweepCommitNoRewardLocal = 199734 325 expSweepCommitNoRewardRemote = 99779 326 expSweepCommitRewardBoth = 296528 327 expSweepCommitRewardLocal = 197698 328 expSweepCommitRewardRemote = 98743 329 sweepFeeRateNoRewardRemoteDust = 450000 330 sweepFeeRateRewardRemoteDust = 385000 331 } 332 333 backupTaskTests = append(backupTaskTests, []backupTaskTest{ 334 genTaskTest( 335 "commit no-reward, both outputs", 336 100, // stateNum 337 200000, // toLocalAmt 338 100000, // toRemoteAmt 339 blobTypeCommitNoReward, // blobType 340 1000, // sweepFeeRate 341 nil, // rewardScript 342 expSweepCommitNoRewardBoth, // expSweepAmt 343 0, // expRewardAmt 344 nil, // bindErr 345 chanType, 346 ), 347 genTaskTest( 348 "commit no-reward, to-local output only", 349 1000, // stateNum 350 200000, // toLocalAmt 351 0, // toRemoteAmt 352 blobTypeCommitNoReward, // blobType 353 1000, // sweepFeeRate 354 nil, // rewardScript 355 expSweepCommitNoRewardLocal, // expSweepAmt 356 0, // expRewardAmt 357 nil, // bindErr 358 chanType, 359 ), 360 genTaskTest( 361 "commit no-reward, to-remote output only", 362 1, // stateNum 363 0, // toLocalAmt 364 100000, // toRemoteAmt 365 blobTypeCommitNoReward, // blobType 366 1000, // sweepFeeRate 367 nil, // rewardScript 368 expSweepCommitNoRewardRemote, // expSweepAmt 369 0, // expRewardAmt 370 nil, // bindErr 371 chanType, 372 ), 373 genTaskTest( 374 "commit no-reward, to-remote output only, creates dust", 375 1, // stateNum 376 0, // toLocalAmt 377 100000, // toRemoteAmt 378 blobTypeCommitNoReward, // blobType 379 sweepFeeRateNoRewardRemoteDust, // sweepFeeRate 380 nil, // rewardScript 381 0, // expSweepAmt 382 0, // expRewardAmt 383 wtpolicy.ErrCreatesDust, // bindErr 384 chanType, 385 ), 386 genTaskTest( 387 "commit no-reward, no outputs, fee rate exceeds inputs", 388 300, // stateNum 389 0, // toLocalAmt 390 0, // toRemoteAmt 391 blobTypeCommitNoReward, // blobType 392 1000, // sweepFeeRate 393 nil, // rewardScript 394 0, // expSweepAmt 395 0, // expRewardAmt 396 wtpolicy.ErrFeeExceedsInputs, // bindErr 397 chanType, 398 ), 399 genTaskTest( 400 "commit no-reward, no outputs, fee rate of 0 creates dust", 401 300, // stateNum 402 0, // toLocalAmt 403 0, // toRemoteAmt 404 blobTypeCommitNoReward, // blobType 405 0, // sweepFeeRate 406 nil, // rewardScript 407 0, // expSweepAmt 408 0, // expRewardAmt 409 wtpolicy.ErrCreatesDust, // bindErr 410 chanType, 411 ), 412 genTaskTest( 413 "commit reward, both outputs", 414 100, // stateNum 415 200000, // toLocalAmt 416 100000, // toRemoteAmt 417 blobTypeCommitReward, // blobType 418 1000, // sweepFeeRate 419 addrScript, // rewardScript 420 expSweepCommitRewardBoth, // expSweepAmt 421 3000, // expRewardAmt 422 nil, // bindErr 423 chanType, 424 ), 425 genTaskTest( 426 "commit reward, to-local output only", 427 1000, // stateNum 428 200000, // toLocalAmt 429 0, // toRemoteAmt 430 blobTypeCommitReward, // blobType 431 1000, // sweepFeeRate 432 addrScript, // rewardScript 433 expSweepCommitRewardLocal, // expSweepAmt 434 2000, // expRewardAmt 435 nil, // bindErr 436 chanType, 437 ), 438 genTaskTest( 439 "commit reward, to-remote output only", 440 1, // stateNum 441 0, // toLocalAmt 442 100000, // toRemoteAmt 443 blobTypeCommitReward, // blobType 444 1000, // sweepFeeRate 445 addrScript, // rewardScript 446 expSweepCommitRewardRemote, // expSweepAmt 447 1000, // expRewardAmt 448 nil, // bindErr 449 chanType, 450 ), 451 genTaskTest( 452 "commit reward, to-remote output only, creates dust", 453 1, // stateNum 454 0, // toLocalAmt 455 100000, // toRemoteAmt 456 blobTypeCommitReward, // blobType 457 sweepFeeRateRewardRemoteDust, // sweepFeeRate 458 addrScript, // rewardScript 459 0, // expSweepAmt 460 0, // expRewardAmt 461 wtpolicy.ErrCreatesDust, // bindErr 462 chanType, 463 ), 464 genTaskTest( 465 "commit reward, no outputs, fee rate exceeds inputs", 466 300, // stateNum 467 0, // toLocalAmt 468 0, // toRemoteAmt 469 blobTypeCommitReward, // blobType 470 1000, // sweepFeeRate 471 addrScript, // rewardScript 472 0, // expSweepAmt 473 0, // expRewardAmt 474 wtpolicy.ErrFeeExceedsInputs, // bindErr 475 chanType, 476 ), 477 genTaskTest( 478 "commit reward, no outputs, fee rate of 0 creates dust", 479 300, // stateNum 480 0, // toLocalAmt 481 0, // toRemoteAmt 482 blobTypeCommitReward, // blobType 483 0, // sweepFeeRate 484 addrScript, // rewardScript 485 0, // expSweepAmt 486 0, // expRewardAmt 487 wtpolicy.ErrCreatesDust, // bindErr 488 chanType, 489 ), 490 }...) 491 } 492 493 for _, test := range backupTaskTests { 494 test := test 495 496 t.Run(test.name, func(t *testing.T) { 497 t.Parallel() 498 499 testBackupTask(t, test) 500 }) 501 } 502 } 503 504 func testBackupTask(t *testing.T, test backupTaskTest) { 505 // Create a new backupTask from the channel id and breach info. 506 task := newBackupTask( 507 &test.chanID, test.breachInfo, test.expSweepScript, 508 test.chanType, chaincfg.TestNet3Params(), 509 ) 510 511 // Assert that all parameters set during initialization are properly 512 // populated. 513 if task.id.ChanID != test.chanID { 514 t.Fatalf("channel id mismatch, want: %s, got: %s", 515 test.chanID, task.id.ChanID) 516 } 517 518 if task.id.CommitHeight != test.breachInfo.RevokedStateNum { 519 t.Fatalf("commit height mismatch, want: %d, got: %d", 520 test.breachInfo.RevokedStateNum, task.id.CommitHeight) 521 } 522 523 if task.totalAmt != test.expTotalAmt { 524 t.Fatalf("total amount mismatch, want: %d, got: %v", 525 test.expTotalAmt, task.totalAmt) 526 } 527 528 if !reflect.DeepEqual(task.breachInfo, test.breachInfo) { 529 t.Fatalf("breach info mismatch, want: %v, got: %v", 530 test.breachInfo, task.breachInfo) 531 } 532 533 if !reflect.DeepEqual(task.toLocalInput, test.expToLocalInput) { 534 t.Fatalf("to-local input mismatch, want: %v, got: %v", 535 test.expToLocalInput, task.toLocalInput) 536 } 537 538 if !reflect.DeepEqual(task.toRemoteInput, test.expToRemoteInput) { 539 t.Fatalf("to-local input mismatch, want: %v, got: %v", 540 test.expToRemoteInput, task.toRemoteInput) 541 } 542 543 // Reconstruct the expected input.Inputs that will be returned by the 544 // task's inputs() method. 545 expInputs := make(map[wire.OutPoint]input.Input) 546 if task.toLocalInput != nil { 547 expInputs[*task.toLocalInput.OutPoint()] = task.toLocalInput 548 } 549 if task.toRemoteInput != nil { 550 expInputs[*task.toRemoteInput.OutPoint()] = task.toRemoteInput 551 } 552 553 // Assert that the inputs method returns the correct slice of 554 // input.Inputs. 555 inputs := task.inputs() 556 if !reflect.DeepEqual(expInputs, inputs) { 557 t.Fatalf("inputs mismatch, want: %v, got: %v", 558 expInputs, inputs) 559 } 560 561 // Now, bind the session to the task. If successful, this locks in the 562 // session's negotiated parameters and allows the backup task to derive 563 // the final free variables in the justice transaction. 564 err := task.bindSession(test.session) 565 if err != test.bindErr { 566 t.Fatalf("expected: %v when binding session, got: %v", 567 test.bindErr, err) 568 } 569 570 // Exit early if the bind was supposed to fail. But first, we check that 571 // all fields set during a bind are still unset. This ensure that a 572 // failed bind doesn't have side-effects if the task is retried with a 573 // different session. 574 if test.bindErr != nil { 575 if task.blobType != 0 { 576 t.Fatalf("blob type should not be set on failed bind, "+ 577 "found: %s", task.blobType) 578 } 579 580 if task.outputs != nil { 581 t.Fatalf("justice outputs should not be set on failed bind, "+ 582 "found: %v", task.outputs) 583 } 584 585 return 586 } 587 588 // Otherwise, the binding succeeded. Assert that all values set during 589 // the bind are properly populated. 590 policy := test.session.Policy 591 if task.blobType != policy.BlobType { 592 t.Fatalf("blob type mismatch, want: %s, got %s", 593 policy.BlobType, task.blobType) 594 } 595 596 // Compute the expected outputs on the justice transaction. 597 var expOutputs = []*wire.TxOut{ 598 { 599 PkScript: test.expSweepScript, 600 Value: test.expSweepAmt, 601 }, 602 } 603 604 // If the policy specifies a reward output, add it to the expected list 605 // of outputs. 606 if test.session.Policy.BlobType.Has(blob.FlagReward) { 607 expOutputs = append(expOutputs, &wire.TxOut{ 608 PkScript: test.expRewardScript, 609 Value: test.expRewardAmt, 610 }) 611 } 612 613 // Assert that the computed outputs match our expected outputs. 614 if !reflect.DeepEqual(expOutputs, task.outputs) { 615 t.Fatalf("justice txn output mismatch, want: %v,\ngot: %v", 616 spew.Sdump(expOutputs), spew.Sdump(task.outputs)) 617 } 618 619 // Now, we'll construct, sign, and encrypt the blob containing the parts 620 // needed to reconstruct the justice transaction. 621 hint, encBlob, err := task.craftSessionPayload(test.signer) 622 if err != nil { 623 t.Fatalf("unable to craft session payload: %v", err) 624 } 625 626 // Verify that the breach hint matches the breach txid's prefix. 627 breachTxID := test.breachInfo.BreachTransaction.TxHash() 628 expHint := blob.NewBreachHintFromHash(&breachTxID) 629 if hint != expHint { 630 t.Fatalf("breach hint mismatch, want: %x, got: %v", 631 expHint, hint) 632 } 633 634 // Decrypt the return blob to obtain the JusticeKit containing its 635 // contents. 636 key := blob.NewBreachKeyFromHash(&breachTxID) 637 jKit, err := blob.Decrypt(key, encBlob, policy.BlobType) 638 if err != nil { 639 t.Fatalf("unable to decrypt blob: %v", err) 640 } 641 642 keyRing := test.breachInfo.KeyRing 643 expToLocalPK := keyRing.ToLocalKey.SerializeCompressed() 644 expRevPK := keyRing.RevocationKey.SerializeCompressed() 645 expToRemotePK := keyRing.ToRemoteKey.SerializeCompressed() 646 647 // Assert that the blob contained the serialized revocation and to-local 648 // pubkeys. 649 if !bytes.Equal(jKit.RevocationPubKey[:], expRevPK) { 650 t.Fatalf("revocation pk mismatch, want: %x, got: %x", 651 expRevPK, jKit.RevocationPubKey[:]) 652 } 653 if !bytes.Equal(jKit.LocalDelayPubKey[:], expToLocalPK) { 654 t.Fatalf("revocation pk mismatch, want: %x, got: %x", 655 expToLocalPK, jKit.LocalDelayPubKey[:]) 656 } 657 658 // Determine if the breach transaction has a to-remote output and/or 659 // to-local output to spend from. Note the seemingly-reversed 660 // nomenclature. 661 hasToRemote := test.breachInfo.LocalOutputSignDesc != nil 662 hasToLocal := test.breachInfo.RemoteOutputSignDesc != nil 663 664 // If the to-remote output is present, assert that the to-remote public 665 // key was included in the blob. 666 if hasToRemote && 667 !bytes.Equal(jKit.CommitToRemotePubKey[:], expToRemotePK) { 668 t.Fatalf("mismatch to-remote pubkey, want: %x, got: %x", 669 expToRemotePK, jKit.CommitToRemotePubKey) 670 } 671 672 // Otherwise if the to-local output is not present, assert that a blank 673 // public key was inserted. 674 if !hasToRemote && 675 !bytes.Equal(jKit.CommitToRemotePubKey[:], zeroPK[:]) { 676 t.Fatalf("mismatch to-remote pubkey, want: %x, got: %x", 677 zeroPK, jKit.CommitToRemotePubKey) 678 } 679 680 // Assert that the CSV is encoded in the blob. 681 if jKit.CSVDelay != test.breachInfo.RemoteDelay { 682 t.Fatalf("mismatch remote delay, want: %d, got: %v", 683 test.breachInfo.RemoteDelay, jKit.CSVDelay) 684 } 685 686 // Assert that the sweep pkscript is included. 687 if !bytes.Equal(jKit.SweepAddress, test.expSweepScript) { 688 t.Fatalf("sweep pkscript mismatch, want: %x, got: %x", 689 test.expSweepScript, jKit.SweepAddress) 690 } 691 692 // Finally, verify that the signatures are encoded in the justice kit. 693 // We don't validate the actual signatures produced here, since at the 694 // moment, it is tested indirectly by other packages and integration 695 // tests. 696 // TODO(conner): include signature validation checks 697 698 emptyToLocalSig := bytes.Equal(jKit.CommitToLocalSig[:], zeroSig[:]) 699 switch { 700 case hasToLocal && emptyToLocalSig: 701 t.Fatalf("to-local signature should not be empty") 702 case !hasToLocal && !emptyToLocalSig: 703 t.Fatalf("to-local signature should be empty") 704 } 705 706 emptyToRemoteSig := bytes.Equal(jKit.CommitToRemoteSig[:], zeroSig[:]) 707 switch { 708 case hasToRemote && emptyToRemoteSig: 709 t.Fatalf("to-remote signature should not be empty") 710 case !hasToRemote && !emptyToRemoteSig: 711 t.Fatalf("to-remote signature should be empty") 712 } 713 }