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  }