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 }