|
1 | 1 | package itest |
2 | 2 |
|
3 | 3 | import ( |
| 4 | + "bytes" |
4 | 5 | "context" |
5 | 6 | "crypto/tls" |
6 | 7 | "net/http" |
7 | 8 | "time" |
8 | 9 |
|
| 10 | + "github.com/btcsuite/btcd/btcec/v2" |
9 | 11 | "github.com/btcsuite/btcd/chaincfg/chainhash" |
| 12 | + "github.com/btcsuite/btcd/txscript" |
10 | 13 | "github.com/btcsuite/btcd/wire" |
| 14 | + "github.com/lightninglabs/taproot-assets/asset" |
| 15 | + "github.com/lightninglabs/taproot-assets/commitment" |
11 | 16 | "github.com/lightninglabs/taproot-assets/fn" |
| 17 | + "github.com/lightninglabs/taproot-assets/internal/test" |
12 | 18 | "github.com/lightninglabs/taproot-assets/proof" |
13 | 19 | "github.com/lightninglabs/taproot-assets/taprpc" |
14 | 20 | "github.com/lightninglabs/taproot-assets/taprpc/mintrpc" |
15 | 21 | "github.com/lightninglabs/taproot-assets/taprpc/tapdevrpc" |
| 22 | + "github.com/lightningnetwork/lnd/lnrpc" |
| 23 | + "github.com/lightningnetwork/lnd/lnrpc/walletrpc" |
16 | 24 | "github.com/stretchr/testify/require" |
| 25 | + "golang.org/x/exp/maps" |
17 | 26 | "golang.org/x/net/http2" |
18 | 27 | ) |
19 | 28 |
|
@@ -334,3 +343,130 @@ func testMintAssetNameCollisionError(t *harnessTest) { |
334 | 343 | collideAssetName := rpcCollideAsset[0].AssetGenesis.Name |
335 | 344 | require.Equal(t.t, commonAssetName, collideAssetName) |
336 | 345 | } |
| 346 | + |
| 347 | +// testMintAssetsWithTapscriptSibling tests that a batch of assets can be minted |
| 348 | +// with a tapscript sibling, and that the genesis output from that mint can be |
| 349 | +// spend via the script path. |
| 350 | +func testMintAssetsWithTapscriptSibling(t *harnessTest) { |
| 351 | + ctxb := context.Background() |
| 352 | + ctxt, cancel := context.WithTimeout(ctxb, defaultWaitTimeout) |
| 353 | + defer cancel() |
| 354 | + |
| 355 | + // Build the tapscript tree. |
| 356 | + sigLockPrivKey := test.RandPrivKey(t.t) |
| 357 | + hashLockPreimage := []byte("foobar") |
| 358 | + hashLockLeaf := test.ScriptHashLock(t.t, hashLockPreimage) |
| 359 | + sigLeaf := test.ScriptSchnorrSig(t.t, sigLockPrivKey.PubKey()) |
| 360 | + siblingTree := txscript.AssembleTaprootScriptTree(hashLockLeaf, sigLeaf) |
| 361 | + |
| 362 | + siblingBranch := txscript.NewTapBranch( |
| 363 | + siblingTree.RootNode.Left(), siblingTree.RootNode.Right(), |
| 364 | + ) |
| 365 | + siblingPreimage := commitment.NewPreimageFromBranch(siblingBranch) |
| 366 | + typedBranch := asset.TapTreeNodesFromBranch(siblingBranch) |
| 367 | + rawBranch := fn.MapOptionZ(asset.GetBranch(typedBranch), asset.ToBranch) |
| 368 | + require.Len(t.t, rawBranch, 2) |
| 369 | + siblingReq := mintrpc.FinalizeBatchRequest_Branch{ |
| 370 | + Branch: &taprpc.TapBranch{ |
| 371 | + LeftTaphash: rawBranch[0], |
| 372 | + RightTaphash: rawBranch[1], |
| 373 | + }, |
| 374 | + } |
| 375 | + |
| 376 | + rpcSimpleAssets := MintAssetsConfirmBatch( |
| 377 | + t.t, t.lndHarness.Miner.Client, t.tapd, simpleAssets, |
| 378 | + WithSiblingBranch(siblingReq), |
| 379 | + ) |
| 380 | + rpcIssuableAssets := MintAssetsConfirmBatch( |
| 381 | + t.t, t.lndHarness.Miner.Client, t.tapd, issuableAssets, |
| 382 | + ) |
| 383 | + |
| 384 | + AssertAssetBalances(t.t, t.tapd, rpcSimpleAssets, rpcIssuableAssets) |
| 385 | + |
| 386 | + // Filter the managed UTXOs to select the genesis UTXO with the |
| 387 | + // tapscript sibling. |
| 388 | + utxos, err := t.tapd.ListUtxos(ctxt, &taprpc.ListUtxosRequest{}) |
| 389 | + require.NoError(t.t, err) |
| 390 | + |
| 391 | + utxoWithTapSibling := func(utxo *taprpc.ManagedUtxo) bool { |
| 392 | + return !bytes.Equal(utxo.TaprootAssetRoot, utxo.MerkleRoot) |
| 393 | + } |
| 394 | + mintingOutputWithSibling := fn.Filter( |
| 395 | + maps.Values(utxos.ManagedUtxos), utxoWithTapSibling, |
| 396 | + ) |
| 397 | + require.Len(t.t, mintingOutputWithSibling, 1) |
| 398 | + genesisWithSibling := mintingOutputWithSibling[0] |
| 399 | + |
| 400 | + // Verify that all assets anchored in the output with the tapscript |
| 401 | + // sibling have the correct sibling preimage. Also verify that the final |
| 402 | + // tweak used for the genesis output is derived from the tapscript |
| 403 | + // sibling created above and the batch Taproot Asset commitment. |
| 404 | + AssertGenesisOutput(t.t, genesisWithSibling, siblingPreimage) |
| 405 | + |
| 406 | + // Extract the fields needed to construct a script path spend, which |
| 407 | + // includes the Taproot Asset commitment root, the final tap tweak, and |
| 408 | + // the internal key. |
| 409 | + mintTapTweak := genesisWithSibling.MerkleRoot |
| 410 | + mintTapTreeRoot := genesisWithSibling.TaprootAssetRoot |
| 411 | + mintInternalKey, err := btcec.ParsePubKey( |
| 412 | + genesisWithSibling.InternalKey, |
| 413 | + ) |
| 414 | + require.NoError(t.t, err) |
| 415 | + |
| 416 | + mintOutputKey := txscript.ComputeTaprootOutputKey( |
| 417 | + mintInternalKey, mintTapTweak, |
| 418 | + ) |
| 419 | + mintOutputKeyIsOdd := mintOutputKey.SerializeCompressed()[0] == 0x03 |
| 420 | + siblingScriptHash := sigLeaf.TapHash() |
| 421 | + |
| 422 | + // Build the control block and witness. |
| 423 | + inclusionProof := bytes.Join( |
| 424 | + [][]byte{siblingScriptHash[:], mintTapTreeRoot}, nil, |
| 425 | + ) |
| 426 | + hashLockControlBlock := txscript.ControlBlock{ |
| 427 | + InternalKey: mintInternalKey, |
| 428 | + OutputKeyYIsOdd: mintOutputKeyIsOdd, |
| 429 | + LeafVersion: txscript.BaseLeafVersion, |
| 430 | + InclusionProof: inclusionProof, |
| 431 | + } |
| 432 | + hashLockControlBlockBytes, err := hashLockControlBlock.ToBytes() |
| 433 | + require.NoError(t.t, err) |
| 434 | + |
| 435 | + hashLockWitness := wire.TxWitness{ |
| 436 | + hashLockPreimage, hashLockLeaf.Script, hashLockControlBlockBytes, |
| 437 | + } |
| 438 | + |
| 439 | + // Make a non-tap output from Bob to use in a TX spending Alice's |
| 440 | + // genesis UTXO. |
| 441 | + burnOutput := MakeOutput( |
| 442 | + t, t.lndHarness.Bob, lnrpc.AddressType_TAPROOT_PUBKEY, 500, |
| 443 | + ) |
| 444 | + |
| 445 | + // Construct and publish the TX. |
| 446 | + genesisOutpoint, err := wire.NewOutPointFromString( |
| 447 | + genesisWithSibling.OutPoint, |
| 448 | + ) |
| 449 | + require.NoError(t.t, err) |
| 450 | + |
| 451 | + burnTx := wire.MsgTx{ |
| 452 | + Version: 2, |
| 453 | + TxIn: []*wire.TxIn{{ |
| 454 | + PreviousOutPoint: *genesisOutpoint, |
| 455 | + Witness: hashLockWitness, |
| 456 | + }}, |
| 457 | + TxOut: []*wire.TxOut{burnOutput}, |
| 458 | + } |
| 459 | + |
| 460 | + var burnTxBuf bytes.Buffer |
| 461 | + require.NoError(t.t, burnTx.Serialize(&burnTxBuf)) |
| 462 | + t.lndHarness.Bob.RPC.PublishTransaction(&walletrpc.Transaction{ |
| 463 | + TxHex: burnTxBuf.Bytes(), |
| 464 | + }) |
| 465 | + |
| 466 | + // Bob should detect the TX, and the resulting confirmed UTXO once |
| 467 | + // a new block is mined. |
| 468 | + t.lndHarness.Miner.AssertNumTxsInMempool(1) |
| 469 | + t.lndHarness.AssertNumUTXOsUnconfirmed(t.lndHarness.Bob, 1) |
| 470 | + t.lndHarness.MineBlocksAndAssertNumTxes(1, 1) |
| 471 | + t.lndHarness.AssertNumUTXOsWithConf(t.lndHarness.Bob, 1, 1, 1) |
| 472 | +} |
0 commit comments