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  }