github.com/decred/dcrlnd@v0.7.6/lntest/itest/lnd_signer_test.go (about)

     1  package itest
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"fmt"
     7  
     8  	"github.com/decred/dcrd/dcrec/secp256k1/v4"
     9  	"github.com/decred/dcrd/dcrutil/v4"
    10  	"github.com/decred/dcrd/txscript/v4"
    11  	"github.com/decred/dcrd/txscript/v4/stdaddr"
    12  	"github.com/decred/dcrd/wire"
    13  	"github.com/decred/dcrlnd/input"
    14  	"github.com/decred/dcrlnd/keychain"
    15  	"github.com/decred/dcrlnd/lnrpc"
    16  	"github.com/decred/dcrlnd/lnrpc/signrpc"
    17  	"github.com/decred/dcrlnd/lnrpc/walletrpc"
    18  	"github.com/decred/dcrlnd/lntest"
    19  	"github.com/stretchr/testify/require"
    20  )
    21  
    22  // testDeriveSharedKey checks the ECDH performed by the endpoint
    23  // DeriveSharedKey. It creates an ephemeral private key, performing an ECDH with
    24  // the node's pubkey and a customized public key to check the validity of the
    25  // result.
    26  func testDeriveSharedKey(net *lntest.NetworkHarness, t *harnessTest) {
    27  	runDeriveSharedKey(t, net.Alice)
    28  }
    29  
    30  // runDeriveSharedKey checks the ECDH performed by the endpoint
    31  // DeriveSharedKey. It creates an ephemeral private key, performing an ECDH with
    32  // the node's pubkey and a customized public key to check the validity of the
    33  // result.
    34  func runDeriveSharedKey(t *harnessTest, alice *lntest.HarnessNode) {
    35  	ctxb := context.Background()
    36  
    37  	// Create an ephemeral key, extracts its public key, and make a
    38  	// PrivKeyECDH using the ephemeral key.
    39  	ephemeralPriv, err := secp256k1.GeneratePrivateKey()
    40  	require.NoError(t.t, err, "failed to create ephemeral key")
    41  
    42  	ephemeralPubBytes := ephemeralPriv.PubKey().SerializeCompressed()
    43  	privKeyECDH := &keychain.PrivKeyECDH{PrivKey: ephemeralPriv}
    44  
    45  	// assertECDHMatch checks the correctness of the ECDH between the
    46  	// ephemeral key and the given public key.
    47  	assertECDHMatch := func(pub *secp256k1.PublicKey,
    48  		req *signrpc.SharedKeyRequest) {
    49  
    50  		ctxt, _ := context.WithTimeout(ctxb, defaultTimeout)
    51  		resp, err := alice.SignerClient.DeriveSharedKey(ctxt, req)
    52  		require.NoError(t.t, err, "calling DeriveSharedKey failed")
    53  
    54  		sharedKey, _ := privKeyECDH.ECDH(pub)
    55  		require.Equal(
    56  			t.t, sharedKey[:], resp.SharedKey,
    57  			"failed to derive the expected key",
    58  		)
    59  	}
    60  
    61  	nodePub, err := secp256k1.ParsePubKey(alice.PubKey[:])
    62  	require.NoError(t.t, err, "failed to parse node pubkey")
    63  
    64  	customizedKeyFamily := int32(keychain.KeyFamilyMultiSig)
    65  	customizedIndex := int32(1)
    66  	customizedPub, err := deriveCustomizedKey(
    67  		ctxb, alice, customizedKeyFamily, customizedIndex,
    68  	)
    69  	require.NoError(t.t, err, "failed to create customized pubkey")
    70  
    71  	// Test DeriveSharedKey with no optional arguments. It will result in
    72  	// performing an ECDH between the ephemeral key and the node's pubkey.
    73  	req := &signrpc.SharedKeyRequest{EphemeralPubkey: ephemeralPubBytes}
    74  	assertECDHMatch(nodePub, req)
    75  
    76  	// Test DeriveSharedKey with a KeyLoc which points to the node's pubkey.
    77  	req = &signrpc.SharedKeyRequest{
    78  		EphemeralPubkey: ephemeralPubBytes,
    79  		KeyLoc: &signrpc.KeyLocator{
    80  			KeyFamily: int32(keychain.KeyFamilyNodeKey),
    81  			KeyIndex:  0,
    82  		},
    83  	}
    84  	assertECDHMatch(nodePub, req)
    85  
    86  	// Test DeriveSharedKey with a KeyLoc being set in KeyDesc. The KeyLoc
    87  	// points to the node's pubkey.
    88  	req = &signrpc.SharedKeyRequest{
    89  		EphemeralPubkey: ephemeralPubBytes,
    90  		KeyDesc: &signrpc.KeyDescriptor{
    91  			KeyLoc: &signrpc.KeyLocator{
    92  				KeyFamily: int32(keychain.KeyFamilyNodeKey),
    93  				KeyIndex:  0,
    94  			},
    95  		},
    96  	}
    97  	assertECDHMatch(nodePub, req)
    98  
    99  	// Test DeriveSharedKey with RawKeyBytes set in KeyDesc. The RawKeyBytes
   100  	// is the node's pubkey bytes, and the KeyFamily is KeyFamilyNodeKey.
   101  	req = &signrpc.SharedKeyRequest{
   102  		EphemeralPubkey: ephemeralPubBytes,
   103  		KeyDesc: &signrpc.KeyDescriptor{
   104  			RawKeyBytes: alice.PubKey[:],
   105  			KeyLoc: &signrpc.KeyLocator{
   106  				KeyFamily: int32(keychain.KeyFamilyNodeKey),
   107  			},
   108  		},
   109  	}
   110  	assertECDHMatch(nodePub, req)
   111  
   112  	// Test DeriveSharedKey with a KeyLoc which points to the customized
   113  	// public key.
   114  	req = &signrpc.SharedKeyRequest{
   115  		EphemeralPubkey: ephemeralPubBytes,
   116  		KeyLoc: &signrpc.KeyLocator{
   117  			KeyFamily: customizedKeyFamily,
   118  			KeyIndex:  customizedIndex,
   119  		},
   120  	}
   121  	assertECDHMatch(customizedPub, req)
   122  
   123  	// Test DeriveSharedKey with a KeyLoc being set in KeyDesc. The KeyLoc
   124  	// points to the customized public key.
   125  	req = &signrpc.SharedKeyRequest{
   126  		EphemeralPubkey: ephemeralPubBytes,
   127  		KeyDesc: &signrpc.KeyDescriptor{
   128  			KeyLoc: &signrpc.KeyLocator{
   129  				KeyFamily: customizedKeyFamily,
   130  				KeyIndex:  customizedIndex,
   131  			},
   132  		},
   133  	}
   134  	assertECDHMatch(customizedPub, req)
   135  
   136  	// Test DeriveSharedKey with RawKeyBytes set in KeyDesc. The RawKeyBytes
   137  	// is the customized public key. The KeyLoc is also set with the family
   138  	// being the customizedKeyFamily.
   139  	req = &signrpc.SharedKeyRequest{
   140  		EphemeralPubkey: ephemeralPubBytes,
   141  		KeyDesc: &signrpc.KeyDescriptor{
   142  			RawKeyBytes: customizedPub.SerializeCompressed(),
   143  			KeyLoc: &signrpc.KeyLocator{
   144  				KeyFamily: customizedKeyFamily,
   145  			},
   146  		},
   147  	}
   148  	assertECDHMatch(customizedPub, req)
   149  
   150  	// assertErrorMatch checks when calling DeriveSharedKey with invalid
   151  	// params, the expected error is returned.
   152  	assertErrorMatch := func(match string, req *signrpc.SharedKeyRequest) {
   153  		ctxt, _ := context.WithTimeout(ctxb, defaultTimeout)
   154  		_, err := alice.SignerClient.DeriveSharedKey(ctxt, req)
   155  		require.Error(t.t, err, "expected to have an error")
   156  		require.Contains(
   157  			t.t, err.Error(), match, "error failed to match",
   158  		)
   159  	}
   160  
   161  	// Test that EphemeralPubkey must be supplied.
   162  	req = &signrpc.SharedKeyRequest{}
   163  	assertErrorMatch("must provide ephemeral pubkey", req)
   164  
   165  	// Test that cannot use both KeyDesc and KeyLoc.
   166  	req = &signrpc.SharedKeyRequest{
   167  		EphemeralPubkey: ephemeralPubBytes,
   168  		KeyDesc: &signrpc.KeyDescriptor{
   169  			RawKeyBytes: customizedPub.SerializeCompressed(),
   170  		},
   171  		KeyLoc: &signrpc.KeyLocator{
   172  			KeyFamily: customizedKeyFamily,
   173  			KeyIndex:  0,
   174  		},
   175  	}
   176  	assertErrorMatch("use either key_desc or key_loc", req)
   177  
   178  	// Test when KeyDesc is used, KeyLoc must be set.
   179  	req = &signrpc.SharedKeyRequest{
   180  		EphemeralPubkey: ephemeralPubBytes,
   181  		KeyDesc: &signrpc.KeyDescriptor{
   182  			RawKeyBytes: alice.PubKey[:],
   183  		},
   184  	}
   185  	assertErrorMatch("key_desc.key_loc must also be set", req)
   186  
   187  	// Test that cannot use both RawKeyBytes and KeyIndex.
   188  	req = &signrpc.SharedKeyRequest{
   189  		EphemeralPubkey: ephemeralPubBytes,
   190  		KeyDesc: &signrpc.KeyDescriptor{
   191  			RawKeyBytes: customizedPub.SerializeCompressed(),
   192  			KeyLoc: &signrpc.KeyLocator{
   193  				KeyFamily: customizedKeyFamily,
   194  				KeyIndex:  1,
   195  			},
   196  		},
   197  	}
   198  	assertErrorMatch("use either raw_key_bytes or key_index", req)
   199  }
   200  
   201  // testSignOutputRaw makes sure that the SignOutputRaw RPC can be used with all
   202  // custom ways of specifying the signing key in the key descriptor/locator.
   203  func testSignOutputRaw(net *lntest.NetworkHarness, t *harnessTest) {
   204  	alice := net.NewNode(t.t, "alice", nil)
   205  	defer shutdownAndAssert(net, t, alice)
   206  	net.SendCoins(t.t, dcrutil.AtomsPerCoin, alice)
   207  	runSignOutputRaw(t, net, alice)
   208  }
   209  
   210  // runSignOutputRaw makes sure that the SignOutputRaw RPC can be used with all
   211  // custom ways of specifying the signing key in the key descriptor/locator.
   212  func runSignOutputRaw(t *harnessTest, net *lntest.NetworkHarness,
   213  	alice *lntest.HarnessNode) {
   214  
   215  	ctxb := context.Background()
   216  	ctxt, cancel := context.WithTimeout(ctxb, defaultTimeout)
   217  	defer cancel()
   218  
   219  	// For the next step, we need a public key. Let's use a special family
   220  	// for this. We want this to be an index of zero.
   221  	// Note(decred): this needs to be one of the known families because the
   222  	// keychain pkg does not derive keys for other families.
   223  	const testCustomKeyFamily = 1
   224  	keyDesc, err := alice.WalletKitClient.DeriveNextKey(
   225  		ctxt, &walletrpc.KeyReq{
   226  			KeyFamily: testCustomKeyFamily,
   227  		},
   228  	)
   229  	require.NoError(t.t, err)
   230  	require.Equal(t.t, int32(0), keyDesc.KeyLoc.KeyIndex)
   231  
   232  	targetPubKey, err := secp256k1.ParsePubKey(keyDesc.RawKeyBytes)
   233  	require.NoError(t.t, err)
   234  
   235  	// First, try with a key descriptor that only sets the public key.
   236  	assertSignOutputRaw(
   237  		t, net, alice, targetPubKey, &signrpc.KeyDescriptor{
   238  			RawKeyBytes: keyDesc.RawKeyBytes,
   239  		},
   240  	)
   241  
   242  	// Now try again, this time only with the (0 index!) key locator.
   243  	assertSignOutputRaw(
   244  		t, net, alice, targetPubKey, &signrpc.KeyDescriptor{
   245  			KeyLoc: &signrpc.KeyLocator{
   246  				KeyFamily: keyDesc.KeyLoc.KeyFamily,
   247  				KeyIndex:  keyDesc.KeyLoc.KeyIndex,
   248  			},
   249  		},
   250  	)
   251  
   252  	// And now test everything again with a new key where we know the index
   253  	// is not 0.
   254  	ctxt, cancel = context.WithTimeout(ctxb, defaultTimeout)
   255  	defer cancel()
   256  	keyDesc, err = alice.WalletKitClient.DeriveNextKey(
   257  		ctxt, &walletrpc.KeyReq{
   258  			KeyFamily: testCustomKeyFamily,
   259  		},
   260  	)
   261  	require.NoError(t.t, err)
   262  	require.Equal(t.t, int32(1), keyDesc.KeyLoc.KeyIndex)
   263  
   264  	targetPubKey, err = secp256k1.ParsePubKey(keyDesc.RawKeyBytes)
   265  	require.NoError(t.t, err)
   266  
   267  	// First, try with a key descriptor that only sets the public key.
   268  	assertSignOutputRaw(
   269  		t, net, alice, targetPubKey, &signrpc.KeyDescriptor{
   270  			RawKeyBytes: keyDesc.RawKeyBytes,
   271  		},
   272  	)
   273  
   274  	// Now try again, this time only with the key locator.
   275  	assertSignOutputRaw(
   276  		t, net, alice, targetPubKey, &signrpc.KeyDescriptor{
   277  			KeyLoc: &signrpc.KeyLocator{
   278  				KeyFamily: keyDesc.KeyLoc.KeyFamily,
   279  				KeyIndex:  keyDesc.KeyLoc.KeyIndex,
   280  			},
   281  		},
   282  	)
   283  }
   284  
   285  // assertSignOutputRaw sends coins to a p2wkh address derived from the given
   286  // target public key and then tries to spend that output again by invoking the
   287  // SignOutputRaw RPC with the key descriptor provided.
   288  func assertSignOutputRaw(t *harnessTest, net *lntest.NetworkHarness,
   289  	alice *lntest.HarnessNode, targetPubKey *secp256k1.PublicKey,
   290  	keyDesc *signrpc.KeyDescriptor) {
   291  
   292  	ctxb := context.Background()
   293  	ctxt, cancel := context.WithTimeout(ctxb, defaultTimeout*3)
   294  	defer cancel()
   295  
   296  	pubKeyHash := dcrutil.Hash160(targetPubKey.SerializeCompressed())
   297  	targetAddr, err := stdaddr.NewAddressPubKeyHashEcdsaSecp256k1V0(
   298  		pubKeyHash, harnessNetParams,
   299  	)
   300  	require.NoError(t.t, err)
   301  	_, targetScript := targetAddr.PaymentScript()
   302  
   303  	// Send some coins to the generated p2wpkh address.
   304  	_, err = alice.SendCoins(ctxt, &lnrpc.SendCoinsRequest{
   305  		Addr:   targetAddr.String(),
   306  		Amount: 800_000,
   307  	})
   308  	require.NoError(t.t, err)
   309  
   310  	// Wait until the TX is found in the mempool.
   311  	txid, err := waitForTxInMempool(net.Miner.Node, minerMempoolTimeout)
   312  	require.NoError(t.t, err)
   313  
   314  	targetOutputIndex := getOutputIndex(
   315  		t, net.Miner, txid, targetAddr.String(),
   316  	)
   317  
   318  	// Clear the mempool.
   319  	mineBlocks(t, net, 1, 1)
   320  
   321  	// Try to spend the output now to a new p2pkh address.
   322  	p2pkhResp, err := alice.NewAddress(ctxt, &lnrpc.NewAddressRequest{
   323  		Type: lnrpc.AddressType_PUBKEY_HASH,
   324  	})
   325  	require.NoError(t.t, err)
   326  
   327  	p2pkhAddr, err := stdaddr.DecodeAddress(
   328  		p2pkhResp.Address, harnessNetParams,
   329  	)
   330  	require.NoError(t.t, err)
   331  
   332  	scriptVersion, p2pkhPkScript := p2pkhAddr.PaymentScript()
   333  	require.NoError(t.t, err)
   334  
   335  	tx := wire.NewMsgTx()
   336  	tx.Version = input.LNTxVersion
   337  	tx.TxIn = []*wire.TxIn{{
   338  		PreviousOutPoint: wire.OutPoint{
   339  			Hash:  *txid,
   340  			Index: uint32(targetOutputIndex),
   341  		},
   342  	}}
   343  	value := int64(800_000 - 3000)
   344  	tx.TxOut = []*wire.TxOut{{
   345  		Version:  scriptVersion,
   346  		PkScript: p2pkhPkScript,
   347  		Value:    value,
   348  	}}
   349  
   350  	var buf bytes.Buffer
   351  	require.NoError(t.t, tx.Serialize(&buf))
   352  
   353  	signResp, err := alice.SignerClient.SignOutputRaw(
   354  		ctxt, &signrpc.SignReq{
   355  			RawTxBytes: buf.Bytes(),
   356  			SignDescs: []*signrpc.SignDescriptor{{
   357  				Output: &signrpc.TxOut{
   358  					PkScript: targetScript,
   359  					Value:    800_000,
   360  				},
   361  				InputIndex:    0,
   362  				KeyDesc:       keyDesc,
   363  				Sighash:       uint32(txscript.SigHashAll),
   364  				WitnessScript: targetScript,
   365  			}},
   366  		},
   367  	)
   368  	require.NoError(t.t, err)
   369  
   370  	sigScript, err := input.WitnessStackToSigScript([][]byte{
   371  		append(signResp.RawSigs[0], byte(txscript.SigHashAll)),
   372  		targetPubKey.SerializeCompressed(),
   373  	})
   374  	require.NoError(t.t, err)
   375  	tx.TxIn[0].SignatureScript = sigScript
   376  
   377  	buf.Reset()
   378  	require.NoError(t.t, tx.Serialize(&buf))
   379  
   380  	_, err = alice.WalletKitClient.PublishTransaction(
   381  		ctxt, &walletrpc.Transaction{
   382  			TxHex: buf.Bytes(),
   383  		},
   384  	)
   385  	require.NoError(t.t, err)
   386  
   387  	// Wait until the spending tx is found.
   388  	txid, err = waitForTxInMempool(net.Miner.Node, minerMempoolTimeout)
   389  	require.NoError(t.t, err)
   390  	p2wkhOutputIndex := getOutputIndex(
   391  		t, net.Miner, txid, p2pkhAddr.String(),
   392  	)
   393  	op := &lnrpc.OutPoint{
   394  		TxidBytes:   txid[:],
   395  		OutputIndex: uint32(p2wkhOutputIndex),
   396  	}
   397  	assertWalletUnspent(t, alice, op)
   398  
   399  	// Mine another block to clean up the mempool and to make sure the spend
   400  	// tx is actually included in a block.
   401  	mineBlocks(t, net, 1, 1)
   402  }
   403  
   404  // deriveCustomizedKey uses the family and index to derive a public key from
   405  // the node's walletkit client.
   406  func deriveCustomizedKey(ctx context.Context, node *lntest.HarnessNode,
   407  	family, index int32) (*secp256k1.PublicKey, error) {
   408  
   409  	ctxt, _ := context.WithTimeout(ctx, defaultTimeout)
   410  	req := &signrpc.KeyLocator{
   411  		KeyFamily: family,
   412  		KeyIndex:  index,
   413  	}
   414  	resp, err := node.WalletKitClient.DeriveKey(ctxt, req)
   415  	if err != nil {
   416  		return nil, fmt.Errorf("failed to derive key: %v", err)
   417  	}
   418  	pub, err := secp256k1.ParsePubKey(resp.RawKeyBytes)
   419  	if err != nil {
   420  		return nil, fmt.Errorf("failed to parse node pubkey: %v", err)
   421  	}
   422  	return pub, nil
   423  }