github.com/decred/dcrlnd@v0.7.6/watchtower/blob/justice_kit_test.go (about) 1 package blob_test 2 3 import ( 4 "bytes" 5 "crypto/rand" 6 "encoding/binary" 7 "io" 8 "reflect" 9 "testing" 10 11 "github.com/decred/dcrd/dcrec/secp256k1/v4" 12 "github.com/decred/dcrd/dcrec/secp256k1/v4/ecdsa" 13 "github.com/decred/dcrd/txscript/v4" 14 "github.com/decred/dcrlnd/input" 15 "github.com/decred/dcrlnd/lnwire" 16 "github.com/decred/dcrlnd/watchtower/blob" 17 "github.com/stretchr/testify/require" 18 ) 19 20 func makePubKey(i uint64) blob.PubKey { 21 var pk blob.PubKey 22 pk[0] = 0x02 23 if i%2 == 1 { 24 pk[0] |= 0x01 25 } 26 binary.BigEndian.PutUint64(pk[1:9], i) 27 return pk 28 } 29 30 func makeSig(i int) lnwire.Sig { 31 var sig lnwire.Sig 32 binary.BigEndian.PutUint64(sig[:8], uint64(i)) 33 return sig 34 } 35 36 func makeAddr(size int) []byte { 37 addr := make([]byte, size) 38 if _, err := io.ReadFull(rand.Reader, addr); err != nil { 39 panic("unable to create addr") 40 } 41 42 return addr 43 } 44 45 type descriptorTest struct { 46 name string 47 encVersion blob.Type 48 decVersion blob.Type 49 sweepAddr []byte 50 revPubKey blob.PubKey 51 delayPubKey blob.PubKey 52 csvDelay uint32 53 commitToLocalSig lnwire.Sig 54 hasCommitToRemote bool 55 commitToRemotePubKey blob.PubKey 56 commitToRemoteSig lnwire.Sig 57 encErr error 58 decErr error 59 } 60 61 var descriptorTests = []descriptorTest{ 62 { 63 name: "to-local only", 64 encVersion: blob.TypeAltruistCommit, 65 decVersion: blob.TypeAltruistCommit, 66 sweepAddr: makeAddr(22), 67 revPubKey: makePubKey(0), 68 delayPubKey: makePubKey(1), 69 csvDelay: 144, 70 commitToLocalSig: makeSig(1), 71 }, 72 { 73 name: "to-local and p2wkh", 74 encVersion: blob.TypeRewardCommit, 75 decVersion: blob.TypeRewardCommit, 76 sweepAddr: makeAddr(22), 77 revPubKey: makePubKey(0), 78 delayPubKey: makePubKey(1), 79 csvDelay: 144, 80 commitToLocalSig: makeSig(1), 81 hasCommitToRemote: true, 82 commitToRemotePubKey: makePubKey(2), 83 commitToRemoteSig: makeSig(2), 84 }, 85 { 86 name: "unknown encrypt version", 87 encVersion: 0, 88 decVersion: blob.TypeAltruistCommit, 89 sweepAddr: makeAddr(34), 90 revPubKey: makePubKey(0), 91 delayPubKey: makePubKey(1), 92 csvDelay: 144, 93 commitToLocalSig: makeSig(1), 94 encErr: blob.ErrUnknownBlobType, 95 }, 96 { 97 name: "unknown decrypt version", 98 encVersion: blob.TypeAltruistCommit, 99 decVersion: 0, 100 sweepAddr: makeAddr(34), 101 revPubKey: makePubKey(0), 102 delayPubKey: makePubKey(1), 103 csvDelay: 144, 104 commitToLocalSig: makeSig(1), 105 decErr: blob.ErrUnknownBlobType, 106 }, 107 { 108 name: "sweep addr length zero", 109 encVersion: blob.TypeAltruistCommit, 110 decVersion: blob.TypeAltruistCommit, 111 sweepAddr: makeAddr(0), 112 revPubKey: makePubKey(0), 113 delayPubKey: makePubKey(1), 114 csvDelay: 144, 115 commitToLocalSig: makeSig(1), 116 }, 117 { 118 name: "sweep addr max size", 119 encVersion: blob.TypeAltruistCommit, 120 decVersion: blob.TypeAltruistCommit, 121 sweepAddr: makeAddr(blob.MaxSweepAddrSize), 122 revPubKey: makePubKey(0), 123 delayPubKey: makePubKey(1), 124 csvDelay: 144, 125 commitToLocalSig: makeSig(1), 126 }, 127 { 128 name: "sweep addr too long", 129 encVersion: blob.TypeAltruistCommit, 130 decVersion: blob.TypeAltruistCommit, 131 sweepAddr: makeAddr(blob.MaxSweepAddrSize + 1), 132 revPubKey: makePubKey(0), 133 delayPubKey: makePubKey(1), 134 csvDelay: 144, 135 commitToLocalSig: makeSig(1), 136 encErr: blob.ErrSweepAddressToLong, 137 }, 138 } 139 140 // TestBlobJusticeKitEncryptDecrypt asserts that encrypting and decrypting a 141 // plaintext blob produces the original. The tests include negative assertions 142 // when passed invalid combinations, and that all successfully encrypted blobs 143 // are of constant size. 144 func TestBlobJusticeKitEncryptDecrypt(t *testing.T) { 145 for _, test := range descriptorTests { 146 t.Run(test.name, func(t *testing.T) { 147 testBlobJusticeKitEncryptDecrypt(t, test) 148 }) 149 } 150 } 151 152 func testBlobJusticeKitEncryptDecrypt(t *testing.T, test descriptorTest) { 153 boj := &blob.JusticeKit{ 154 BlobType: test.encVersion, 155 SweepAddress: test.sweepAddr, 156 RevocationPubKey: test.revPubKey, 157 LocalDelayPubKey: test.delayPubKey, 158 CSVDelay: test.csvDelay, 159 CommitToLocalSig: test.commitToLocalSig, 160 CommitToRemotePubKey: test.commitToRemotePubKey, 161 CommitToRemoteSig: test.commitToRemoteSig, 162 } 163 164 // Generate a random encryption key for the blob. The key is 165 // sized at 32 byte, as in practice we will be using the remote 166 // party's commitment txid as the key. 167 var key blob.BreachKey 168 _, err := rand.Read(key[:]) 169 if err != nil { 170 t.Fatalf("unable to generate blob encryption key: %v", err) 171 } 172 173 // Encrypt the blob plaintext using the generated key and 174 // target version for this test. 175 ctxt, err := boj.Encrypt(key) 176 if err != test.encErr { 177 t.Fatalf("unable to encrypt blob: %v", err) 178 } else if test.encErr != nil { 179 // If the test expected an encryption failure, we can 180 // continue to the next test. 181 return 182 } 183 184 // Ensure that all encrypted blobs are padded out to the same 185 // size: 282 bytes for version 0. 186 if len(ctxt) != blob.Size(test.encVersion) { 187 t.Fatalf("expected blob to have size %d, got %d instead", 188 blob.Size(test.encVersion), len(ctxt)) 189 190 } 191 192 // Decrypt the encrypted blob, reconstructing the original 193 // blob plaintext from the decrypted contents. We use the target 194 // decryption version specified by this test case. 195 boj2, err := blob.Decrypt(key, ctxt, test.decVersion) 196 if err != test.decErr { 197 t.Fatalf("unable to decrypt blob: %v", err) 198 } else if test.decErr != nil { 199 // If the test expected an decryption failure, we can 200 // continue to the next test. 201 return 202 } 203 204 // Check that the decrypted blob properly reports whether it has 205 // a to-remote output or not. 206 if boj2.HasCommitToRemoteOutput() != test.hasCommitToRemote { 207 t.Fatalf("expected blob has_to_remote to be %v, got %v", 208 test.hasCommitToRemote, boj2.HasCommitToRemoteOutput()) 209 } 210 211 // Check that the original blob plaintext matches the 212 // one reconstructed from the encrypted blob. 213 if !reflect.DeepEqual(boj, boj2) { 214 t.Fatalf("decrypted plaintext does not match original, "+ 215 "want: %v, got %v", boj, boj2) 216 } 217 } 218 219 type remoteWitnessTest struct { 220 name string 221 blobType blob.Type 222 expWitnessScript func(pk *secp256k1.PublicKey) []byte 223 } 224 225 // TestJusticeKitRemoteWitnessConstruction tests that a JusticeKit returns the 226 // proper to-remote witnes script and to-remote witness stack. This should be 227 // equivalent to p2wkh spend. 228 func TestJusticeKitRemoteWitnessConstruction(t *testing.T) { 229 tests := []remoteWitnessTest{ 230 { 231 name: "legacy commitment", 232 blobType: blob.Type(blob.FlagCommitOutputs), 233 expWitnessScript: func(pk *secp256k1.PublicKey) []byte { 234 return pk.SerializeCompressed() 235 }, 236 }, 237 { 238 name: "anchor commitment", 239 blobType: blob.Type(blob.FlagCommitOutputs | 240 blob.FlagAnchorChannel), 241 expWitnessScript: func(pk *secp256k1.PublicKey) []byte { 242 script, _ := input.CommitScriptToRemoteConfirmed(pk) 243 return script 244 }, 245 }, 246 } 247 for _, test := range tests { 248 test := test 249 t.Run(test.name, func(t *testing.T) { 250 testJusticeKitRemoteWitnessConstruction(t, test) 251 }) 252 } 253 } 254 255 func testJusticeKitRemoteWitnessConstruction( 256 t *testing.T, test remoteWitnessTest) { 257 258 // Generate the to-remote pubkey. 259 toRemotePrivKey, err := secp256k1.GeneratePrivateKey() 260 require.Nil(t, err) 261 262 // Copy the to-remote pubkey into the format expected by our justice 263 // kit. 264 var toRemotePubKey blob.PubKey 265 copy(toRemotePubKey[:], toRemotePrivKey.PubKey().SerializeCompressed()) 266 267 // Sign a message using the to-remote private key. The exact message 268 // doesn't matter as we won't be validating the signature's validity. 269 digest := bytes.Repeat([]byte("a"), 32) 270 rawToRemoteSig := ecdsa.Sign(toRemotePrivKey, digest) 271 272 // Convert the DER-encoded signature into a fixed-size sig. 273 commitToRemoteSig, err := lnwire.NewSigFromSignature(rawToRemoteSig) 274 require.Nil(t, err) 275 276 // Populate the justice kit fields relevant to the to-remote output. 277 justiceKit := &blob.JusticeKit{ 278 BlobType: test.blobType, 279 CommitToRemotePubKey: toRemotePubKey, 280 CommitToRemoteSig: commitToRemoteSig, 281 } 282 283 // Now, compute the to-remote witness script returned by the justice 284 // kit. 285 toRemoteScript, err := justiceKit.CommitToRemoteWitnessScript() 286 require.Nil(t, err) 287 288 // Assert this is exactly the to-remote, compressed pubkey. 289 expToRemoteScript := test.expWitnessScript(toRemotePrivKey.PubKey()) 290 require.Equal(t, expToRemoteScript, toRemoteScript) 291 292 // Next, compute the to-remote witness stack, which should be a p2wkh 293 // witness stack consisting solely of a signature. 294 toRemoteWitnessStack, err := justiceKit.CommitToRemoteWitnessStack() 295 require.Nil(t, err) 296 297 // Compute the expected first element, by appending a sighash all byte 298 // to our raw DER-encoded signature. 299 rawToRemoteSigWithSigHash := append( 300 rawToRemoteSig.Serialize(), byte(txscript.SigHashAll), 301 ) 302 303 // Assert that the expected witness stack is returned. 304 expWitnessStack := [][]byte{ 305 rawToRemoteSigWithSigHash, 306 } 307 require.Equal(t, expWitnessStack, toRemoteWitnessStack) 308 309 // Finally, set the CommitToRemotePubKey to be a blank value. 310 justiceKit.CommitToRemotePubKey = blob.PubKey{} 311 312 // When trying to compute the witness script, this should now return 313 // ErrNoCommitToRemoteOutput since a valid pubkey could not be parsed 314 // from CommitToRemotePubKey. 315 _, err = justiceKit.CommitToRemoteWitnessScript() 316 require.Error(t, blob.ErrNoCommitToRemoteOutput, err) 317 } 318 319 // TestJusticeKitToLocalWitnessConstruction tests that a JusticeKit returns the 320 // proper to-local witness script and to-local witness stack for spending the 321 // revocation path. 322 func TestJusticeKitToLocalWitnessConstruction(t *testing.T) { 323 csvDelay := uint32(144) 324 325 // Generate the revocation and delay private keys. 326 revPrivKey, err := secp256k1.GeneratePrivateKey() 327 require.Nil(t, err) 328 if err != nil { 329 t.Fatalf("unable to generate revocation priv key: %v", err) 330 } 331 332 delayPrivKey, err := secp256k1.GeneratePrivateKey() 333 require.Nil(t, err) 334 335 // Copy the revocation and delay pubkeys into the format expected by our 336 // justice kit. 337 var revPubKey blob.PubKey 338 copy(revPubKey[:], revPrivKey.PubKey().SerializeCompressed()) 339 340 var delayPubKey blob.PubKey 341 copy(delayPubKey[:], delayPrivKey.PubKey().SerializeCompressed()) 342 343 // Sign a message using the revocation private key. The exact message 344 // doesn't matter as we won't be validating the signature's validity. 345 digest := bytes.Repeat([]byte("a"), 32) 346 rawRevSig := ecdsa.Sign(revPrivKey, digest) 347 348 // Convert the DER-encoded signature into a fixed-size sig. 349 commitToLocalSig, err := lnwire.NewSigFromSignature(rawRevSig) 350 require.Nil(t, err) 351 352 // Populate the justice kit with fields relevant to the to-local output. 353 justiceKit := &blob.JusticeKit{ 354 CSVDelay: csvDelay, 355 RevocationPubKey: revPubKey, 356 LocalDelayPubKey: delayPubKey, 357 CommitToLocalSig: commitToLocalSig, 358 } 359 360 // Compute the expected to-local script, which is a function of the CSV 361 // delay, revocation pubkey and delay pubkey. 362 expToLocalScript, err := input.CommitScriptToSelf( 363 csvDelay, delayPrivKey.PubKey(), revPrivKey.PubKey(), 364 ) 365 require.Nil(t, err) 366 367 // Compute the to-local script that is returned by the justice kit. 368 toLocalScript, err := justiceKit.CommitToLocalWitnessScript() 369 require.Nil(t, err) 370 371 // Assert that the expected to-local script matches the actual script. 372 require.Equal(t, expToLocalScript, toLocalScript) 373 374 // Next, compute the to-local witness stack returned by the justice kit. 375 toLocalWitnessStack, err := justiceKit.CommitToLocalRevokeWitnessStack() 376 require.Nil(t, err) 377 378 // Compute the expected signature in the bottom element of the stack, by 379 // appending a sighash all flag to the raw DER signature. 380 rawRevSigWithSigHash := append( 381 rawRevSig.Serialize(), byte(txscript.SigHashAll), 382 ) 383 384 // Finally, validate against our expected witness stack. 385 expWitnessStack := [][]byte{ 386 rawRevSigWithSigHash, 387 {1}, 388 } 389 require.Equal(t, expWitnessStack, toLocalWitnessStack) 390 }