github.com/decred/dcrlnd@v0.7.6/watchtower/blob/justice_kit.go (about) 1 package blob 2 3 import ( 4 "bytes" 5 "crypto/rand" 6 "encoding/binary" 7 "errors" 8 "fmt" 9 "io" 10 11 "golang.org/x/crypto/chacha20poly1305" 12 13 "github.com/decred/dcrd/dcrec/secp256k1/v4" 14 "github.com/decred/dcrd/txscript/v4" 15 "github.com/decred/dcrlnd/input" 16 "github.com/decred/dcrlnd/lnwire" 17 ) 18 19 const ( 20 // NonceSize is the length of a chacha20poly1305 nonce, 24 bytes. 21 NonceSize = chacha20poly1305.NonceSizeX 22 23 // KeySize is the length of a chacha20poly1305 key, 32 bytes. 24 KeySize = chacha20poly1305.KeySize 25 26 // CiphertextExpansion is the number of bytes padded to a plaintext 27 // encrypted with chacha20poly1305, which comes from a 16-byte MAC. 28 CiphertextExpansion = 16 29 30 // V0PlaintextSize is the plaintext size of a version 0 encoded blob. 31 // sweep address length: 1 byte 32 // padded sweep address: 42 bytes 33 // revocation pubkey: 33 bytes 34 // local delay pubkey: 33 bytes 35 // csv delay: 4 bytes 36 // commit to-local revocation sig: 64 bytes 37 // commit to-remote pubkey: 33 bytes, maybe blank 38 // commit to-remote sig: 64 bytes, maybe blank 39 V0PlaintextSize = 274 40 41 // MaxSweepAddrSize defines the maximum sweep address size that can be 42 // encoded in a blob. 43 MaxSweepAddrSize = 42 44 ) 45 46 // Size returns the size of the encoded-and-encrypted blob in bytes. 47 // 48 // nonce: 24 bytes 49 // enciphered plaintext: n bytes 50 // MAC: 16 bytes 51 func Size(blobType Type) int { 52 return NonceSize + PlaintextSize(blobType) + CiphertextExpansion 53 } 54 55 // PlaintextSize returns the size of the encoded-but-unencrypted blob in bytes. 56 func PlaintextSize(blobType Type) int { 57 switch { 58 case blobType.Has(FlagCommitOutputs): 59 return V0PlaintextSize 60 default: 61 return 0 62 } 63 } 64 65 var ( 66 // byteOrder specifies a big-endian encoding of all integer values. 67 byteOrder = binary.BigEndian 68 69 // ErrUnknownBlobType signals that we don't understand the requested 70 // blob encoding scheme. 71 ErrUnknownBlobType = errors.New("unknown blob type") 72 73 // ErrCiphertextTooSmall is a decryption error signaling that the 74 // ciphertext is smaller than the ciphertext expansion factor. 75 ErrCiphertextTooSmall = errors.New( 76 "ciphertext is too small for chacha20poly1305", 77 ) 78 79 // ErrNoCommitToRemoteOutput is returned when trying to retrieve the 80 // commit to-remote output from the blob, though none exists. 81 ErrNoCommitToRemoteOutput = errors.New( 82 "cannot obtain commit to-remote p2wkh output script from blob", 83 ) 84 85 // ErrSweepAddressToLong is returned when trying to encode or decode a 86 // sweep address with length greater than the maximum length of 42 87 // bytes, which supports p2wkh and p2sh addresses. 88 ErrSweepAddressToLong = fmt.Errorf( 89 "sweep address must be less than or equal to %d bytes long", 90 MaxSweepAddrSize, 91 ) 92 ) 93 94 // PubKey is a 33-byte, serialized compressed public key. 95 type PubKey [33]byte 96 97 // isCompressedPubKey returns true the the passed serialized public key has 98 // been encoded in compressed format, and false otherwise. 99 func isCompressedPubKey(pubKey []byte) bool { 100 const ( 101 PubKeyBytesLenCompressed = 33 102 pubkeyCompressed byte = 0x2 // y_bit + x coord 103 ) 104 105 // The public key is only compressed if it is the correct length and 106 // the format (first byte) is one of the compressed pubkey values. 107 return len(pubKey) == PubKeyBytesLenCompressed && 108 (pubKey[0]&^byte(0x1) == pubkeyCompressed) 109 } 110 111 // JusticeKit is lé Blob of Justice. The JusticeKit contains information 112 // required to construct a justice transaction, that sweeps a remote party's 113 // revoked commitment transaction. It supports encryption and decryption using 114 // chacha20poly1305, allowing the client to encrypt the contents of the blob, 115 // and for a watchtower to later decrypt if action must be taken. The encoding 116 // format is versioned to allow future extensions. 117 type JusticeKit struct { 118 // BlobType encodes a bitfield that inform the tower of various features 119 // requested by the client when resolving a breach. Examples include 120 // whether the justice transaction contains a reward for the tower, or 121 // whether the channel is a legacy or anchor channel. 122 // 123 // NOTE: This value is not serialized in the encrypted payload. It is 124 // stored separately and added to the JusticeKit after decryption. 125 BlobType Type 126 127 // SweepAddress is the witness program of the output where the client's 128 // fund will be deposited. This value is included in the blobs, as 129 // opposed to the session info, such that the sweep addresses can't be 130 // correlated across sessions and/or towers. 131 // 132 // NOTE: This is chosen to be the length of a maximally sized witness 133 // program. 134 SweepAddress []byte 135 136 // RevocationPubKey is the compressed pubkey that guards the revocation 137 // clause of the remote party's to-local output. 138 RevocationPubKey PubKey 139 140 // LocalDelayPubKey is the compressed pubkey in the to-local script of 141 // the remote party, which guards the path where the remote party 142 // claims their commitment output. 143 LocalDelayPubKey PubKey 144 145 // CSVDelay is the relative timelock in the remote party's to-local 146 // output, which the remote party must wait out before sweeping their 147 // commitment output. 148 CSVDelay uint32 149 150 // CommitToLocalSig is a signature under RevocationPubKey using 151 // SIGHASH_ALL. 152 CommitToLocalSig lnwire.Sig 153 154 // CommitToRemotePubKey is the public key in the to-remote output of the revoked 155 // commitment transaction. 156 // 157 // NOTE: This value is only used if it contains a valid compressed 158 // public key. 159 CommitToRemotePubKey PubKey 160 161 // CommitToRemoteSig is a signature under CommitToRemotePubKey using SIGHASH_ALL. 162 // 163 // NOTE: This value is only used if CommitToRemotePubKey contains a valid 164 // compressed public key. 165 CommitToRemoteSig lnwire.Sig 166 } 167 168 // CommitToLocalWitnessScript returns the serialized redeem script for the 169 // commitment to-local output. 170 func (b *JusticeKit) CommitToLocalWitnessScript() ([]byte, error) { 171 revocationPubKey, err := secp256k1.ParsePubKey(b.RevocationPubKey[:]) 172 if err != nil { 173 return nil, err 174 } 175 176 localDelayedPubKey, err := secp256k1.ParsePubKey(b.LocalDelayPubKey[:]) 177 if err != nil { 178 return nil, err 179 } 180 181 return input.CommitScriptToSelf( 182 b.CSVDelay, localDelayedPubKey, revocationPubKey, 183 ) 184 } 185 186 // CommitToLocalRevokeWitnessStack constructs a witness stack spending the 187 // revocation clause of the commitment to-local output. 188 // 189 // <revocation-sig> 1 190 func (b *JusticeKit) CommitToLocalRevokeWitnessStack() ([][]byte, error) { 191 toLocalSig, err := b.CommitToLocalSig.ToSignature() 192 if err != nil { 193 return nil, err 194 } 195 196 witnessStack := make([][]byte, 2) 197 witnessStack[0] = append(toLocalSig.Serialize(), 198 byte(txscript.SigHashAll)) 199 witnessStack[1] = []byte{1} 200 201 return witnessStack, nil 202 } 203 204 // HasCommitToRemoteOutput returns true if the blob contains a to-remote p2wkh 205 // pubkey. 206 func (b *JusticeKit) HasCommitToRemoteOutput() bool { 207 return isCompressedPubKey(b.CommitToRemotePubKey[:]) 208 } 209 210 // CommitToRemoteWitnessScript returns the witness script for the commitment 211 // to-remote output given the blob type. The script returned will either be for 212 // a p2pkh to-remote output or an p2sh anchor to-remote output which includes 213 // a CSV delay. 214 func (b *JusticeKit) CommitToRemoteWitnessScript() ([]byte, error) { 215 if !isCompressedPubKey(b.CommitToRemotePubKey[:]) { 216 return nil, ErrNoCommitToRemoteOutput 217 } 218 219 // If this is a blob for an anchor channel, we'll return the p2wsh 220 // output containing a CSV delay of 1. 221 if b.BlobType.IsAnchorChannel() { 222 pk, err := secp256k1.ParsePubKey( 223 b.CommitToRemotePubKey[:], 224 ) 225 if err != nil { 226 return nil, err 227 } 228 229 return input.CommitScriptToRemoteConfirmed(pk) 230 } 231 232 return b.CommitToRemotePubKey[:], nil 233 } 234 235 // CommitToRemoteWitnessStack returns a witness stack spending the commitment 236 // to-remote output, which consists of a single signature satisfying either the 237 // legacy or anchor witness scripts. 238 // 239 // <to-remote-sig> 240 func (b *JusticeKit) CommitToRemoteWitnessStack() ([][]byte, error) { 241 toRemoteSig, err := b.CommitToRemoteSig.ToSignature() 242 if err != nil { 243 return nil, err 244 } 245 246 witnessStack := make([][]byte, 1) 247 witnessStack[0] = append(toRemoteSig.Serialize(), 248 byte(txscript.SigHashAll)) 249 250 return witnessStack, nil 251 } 252 253 // Encrypt encodes the blob of justice using encoding version, and then 254 // creates a ciphertext using chacha20poly1305 under the chosen (nonce, key) 255 // pair. 256 // 257 // NOTE: It is the caller's responsibility to ensure that this method is only 258 // called once for a given (nonce, key) pair. 259 func (b *JusticeKit) Encrypt(key BreachKey) ([]byte, error) { 260 // Encode the plaintext using the provided version, to obtain the 261 // plaintext bytes. 262 var ptxtBuf bytes.Buffer 263 err := b.encode(&ptxtBuf, b.BlobType) 264 if err != nil { 265 return nil, err 266 } 267 268 // Create a new chacha20poly1305 cipher, using a 32-byte key. 269 cipher, err := chacha20poly1305.NewX(key[:]) 270 if err != nil { 271 return nil, err 272 } 273 274 // Allocate the ciphertext, which will contain the nonce, encrypted 275 // plaintext and MAC. 276 plaintext := ptxtBuf.Bytes() 277 ciphertext := make([]byte, Size(b.BlobType)) 278 279 // Generate a random 24-byte nonce in the ciphertext's prefix. 280 nonce := ciphertext[:NonceSize] 281 if _, err := io.ReadFull(rand.Reader, nonce); err != nil { 282 return nil, err 283 } 284 285 // Finally, encrypt the plaintext using the given nonce, storing the 286 // result in the ciphertext buffer. 287 cipher.Seal(ciphertext[NonceSize:NonceSize], nonce, plaintext, nil) 288 289 return ciphertext, nil 290 } 291 292 // Decrypt unenciphers a blob of justice by decrypting the ciphertext using 293 // chacha20poly1305 with the chosen (nonce, key) pair. The internal plaintext is 294 // then deserialized using the given encoding version. 295 func Decrypt(key BreachKey, ciphertext []byte, 296 blobType Type) (*JusticeKit, error) { 297 298 // Fail if the blob's overall length is less than required for the nonce 299 // and expansion factor. 300 if len(ciphertext) < NonceSize+CiphertextExpansion { 301 return nil, ErrCiphertextTooSmall 302 } 303 304 // Create a new chacha20poly1305 cipher, using a 32-byte key. 305 cipher, err := chacha20poly1305.NewX(key[:]) 306 if err != nil { 307 return nil, err 308 } 309 310 // Allocate the final buffer that will contain the blob's plaintext 311 // bytes, which is computed by subtracting the ciphertext expansion 312 // factor from the blob's length. 313 plaintext := make([]byte, len(ciphertext)-CiphertextExpansion) 314 315 // Decrypt the ciphertext, placing the resulting plaintext in our 316 // plaintext buffer. 317 nonce := ciphertext[:NonceSize] 318 _, err = cipher.Open(plaintext[:0], nonce, ciphertext[NonceSize:], nil) 319 if err != nil { 320 return nil, err 321 } 322 323 // If decryption succeeded, we will then decode the plaintext bytes 324 // using the specified blob version. 325 boj := &JusticeKit{ 326 BlobType: blobType, 327 } 328 err = boj.decode(bytes.NewReader(plaintext), blobType) 329 if err != nil { 330 return nil, err 331 } 332 333 return boj, nil 334 } 335 336 // encode serializes the JusticeKit according to the version, returning an 337 // error if the version is unknown. 338 func (b *JusticeKit) encode(w io.Writer, blobType Type) error { 339 switch { 340 case blobType.Has(FlagCommitOutputs): 341 return b.encodeV0(w) 342 default: 343 return ErrUnknownBlobType 344 } 345 } 346 347 // decode deserializes the JusticeKit according to the version, returning an 348 // error if the version is unknown. 349 func (b *JusticeKit) decode(r io.Reader, blobType Type) error { 350 switch { 351 case blobType.Has(FlagCommitOutputs): 352 return b.decodeV0(r) 353 default: 354 return ErrUnknownBlobType 355 } 356 } 357 358 // encodeV0 encodes the JusticeKit using the version 0 encoding scheme to the 359 // provided io.Writer. The encoding supports sweeping of the commit to-local 360 // output, and optionally the commit to-remote output. The encoding produces a 361 // constant-size plaintext size of 274 bytes. 362 // 363 // blob version 0 plaintext encoding: 364 // 365 // sweep address length: 1 byte 366 // padded sweep address: 42 bytes 367 // revocation pubkey: 33 bytes 368 // local delay pubkey: 33 bytes 369 // csv delay: 4 bytes 370 // commit to-local revocation sig: 64 bytes 371 // commit to-remote pubkey: 33 bytes, maybe blank 372 // commit to-remote sig: 64 bytes, maybe blank 373 func (b *JusticeKit) encodeV0(w io.Writer) error { 374 // Assert the sweep address length is sane. 375 if len(b.SweepAddress) > MaxSweepAddrSize { 376 return ErrSweepAddressToLong 377 } 378 379 // Write the actual length of the sweep address as a single byte. 380 err := binary.Write(w, byteOrder, uint8(len(b.SweepAddress))) 381 if err != nil { 382 return err 383 } 384 385 // Pad the sweep address to our maximum length of 42 bytes. 386 var sweepAddressBuf [MaxSweepAddrSize]byte 387 copy(sweepAddressBuf[:], b.SweepAddress) 388 389 // Write padded 42-byte sweep address. 390 _, err = w.Write(sweepAddressBuf[:]) 391 if err != nil { 392 return err 393 } 394 395 // Write 33-byte revocation public key. 396 _, err = w.Write(b.RevocationPubKey[:]) 397 if err != nil { 398 return err 399 } 400 401 // Write 33-byte local delay public key. 402 _, err = w.Write(b.LocalDelayPubKey[:]) 403 if err != nil { 404 return err 405 } 406 407 // Write 4-byte CSV delay. 408 err = binary.Write(w, byteOrder, b.CSVDelay) 409 if err != nil { 410 return err 411 } 412 413 // Write 64-byte revocation signature for commit to-local output. 414 _, err = w.Write(b.CommitToLocalSig[:]) 415 if err != nil { 416 return err 417 } 418 419 // Write 33-byte commit to-remote public key, which may be blank. 420 _, err = w.Write(b.CommitToRemotePubKey[:]) 421 if err != nil { 422 return err 423 } 424 425 // Write 64-byte commit to-remote signature, which may be blank. 426 _, err = w.Write(b.CommitToRemoteSig[:]) 427 return err 428 } 429 430 // decodeV0 reconstructs a JusticeKit from the io.Reader, using version 0 431 // encoding scheme. This will parse a constant size input stream of 274 bytes to 432 // recover information for the commit to-local output, and possibly the commit 433 // to-remote output. 434 // 435 // blob version 0 plaintext encoding: 436 // 437 // sweep address length: 1 byte 438 // padded sweep address: 42 bytes 439 // revocation pubkey: 33 bytes 440 // local delay pubkey: 33 bytes 441 // csv delay: 4 bytes 442 // commit to-local revocation sig: 64 bytes 443 // commit to-remote pubkey: 33 bytes, maybe blank 444 // commit to-remote sig: 64 bytes, maybe blank 445 func (b *JusticeKit) decodeV0(r io.Reader) error { 446 // Read the sweep address length as a single byte. 447 var sweepAddrLen uint8 448 err := binary.Read(r, byteOrder, &sweepAddrLen) 449 if err != nil { 450 return err 451 } 452 453 // Assert the sweep address length is sane. 454 if sweepAddrLen > MaxSweepAddrSize { 455 return ErrSweepAddressToLong 456 } 457 458 // Read padded 42-byte sweep address. 459 var sweepAddressBuf [MaxSweepAddrSize]byte 460 _, err = io.ReadFull(r, sweepAddressBuf[:]) 461 if err != nil { 462 return err 463 } 464 465 // Parse sweep address from padded buffer. 466 b.SweepAddress = make([]byte, sweepAddrLen) 467 copy(b.SweepAddress, sweepAddressBuf[:]) 468 469 // Read 33-byte revocation public key. 470 _, err = io.ReadFull(r, b.RevocationPubKey[:]) 471 if err != nil { 472 return err 473 } 474 475 // Read 33-byte local delay public key. 476 _, err = io.ReadFull(r, b.LocalDelayPubKey[:]) 477 if err != nil { 478 return err 479 } 480 481 // Read 4-byte CSV delay. 482 err = binary.Read(r, byteOrder, &b.CSVDelay) 483 if err != nil { 484 return err 485 } 486 487 // Read 64-byte revocation signature for commit to-local output. 488 _, err = io.ReadFull(r, b.CommitToLocalSig[:]) 489 if err != nil { 490 return err 491 } 492 493 var ( 494 commitToRemotePubkey PubKey 495 commitToRemoteSig lnwire.Sig 496 ) 497 498 // Read 33-byte commit to-remote public key, which may be discarded. 499 _, err = io.ReadFull(r, commitToRemotePubkey[:]) 500 if err != nil { 501 return err 502 } 503 504 // Read 64-byte commit to-remote signature, which may be discarded. 505 _, err = io.ReadFull(r, commitToRemoteSig[:]) 506 if err != nil { 507 return err 508 } 509 510 // Only populate the commit to-remote fields in the decoded blob if a 511 // valid compressed public key was read from the reader. 512 if isCompressedPubKey(commitToRemotePubkey[:]) { 513 b.CommitToRemotePubKey = commitToRemotePubkey 514 b.CommitToRemoteSig = commitToRemoteSig 515 } 516 517 return nil 518 }