Repository: lightningnetwork/lnd
Pull request #8683 (2024-04-24 09:39:28)

[1/?]: multi: add ability to fund+use musig2 channels that commit to a tapscript root
Replaces https://github.com/lightningnetwork/lnd/pull/8546 (with correct base branch and upstream feature branch).

In this PR, we add the initial plumbing that'll allow users to create and use musig2 channels that also commit to a tapscript root. Along the way, we start to store a new optional tapscript root in the `channeldb.OpenChannel` struct within the existing TLV stream appended to the older hard coded byte stream. The musig2 session itself will now conditionally pass in a tapscript root to the key aggregation and signing operations. We've also hooked up the lower half of the funding flow (lnwallet reservations) as well to ensure the new field gets propagated all the way down the relevant call stacks. 

Link to all PRs in the saga:
 - 1/?: https://github.com/lightningnetwork/lnd/pull/8683
 - 2/?: https://github.com/lightningnetwork/lnd/pull/8684
 - 3/?: https://github.com/lightningnetwork/lnd/pull/8622
 - 4/?: https://github.com/lightningnetwork/lnd/pull/8632
 - 5/?: https://github.com/lightningnetwork/lnd/pull/8641

github-actions[bot]: ## Pull reviewers stats
Stats of the last 30 days for [lnd](https://github.com/lightningnetwork/lnd):
|                                                                                                                                                                     | User              | Total reviews  | Time to review                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               | Total comments |
| ------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------- | -------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------- |
| <a href="https://github.com/guggero"><img src="https://avatars.githubusercontent.com/u/1008879?u=2c337f92b7dbc69b8aaff7885926f2bc1865db0d&v=4" width="32"></a>      | guggero<br/>🥇    | **21**<br/>▀▀▀ | [12h 42m](https://app.flowwer.dev/charts/review-time/~(u~(i~'1008879~n~'guggero)~p~30~r~(~(d~'say5au~t~'9v1)~(d~'sbbaqr~t~'18hu)~(d~'sbbc7l~t~'8uwq)~(d~'sbev8g~t~'151s)~(d~'sbgjbg~t~'49c4)~(d~'sbo1zd~t~'k87i)~(d~'sbobun~t~'89af)~(d~'sborc4~t~'271)~(d~'sbosgp~t~'dbhj)~(d~'sbtg9k~t~'kur)~(d~'sbth4q~t~'qe6c)~(d~'sbz17s~t~'4bwc)~(d~'sc2sjk~t~'ba3)~(d~'sc4lbj~t~'nlp)~(d~'sc56x5~t~'47h)~(d~'sc6dmt~t~'q32)~(d~'sc8eqd~t~'2l58)~(d~'scc295~t~'fnc)~(d~'scc3d1~t~'svws)~(d~'sccdvx~t~'noqu)~(d~'sccg3g~t~'thn)~(d~'scdv0e~t~'rt2)~(d~'scdxq4~t~'8e)~(d~'scdz5f~t~'1np))))<br/>                                                                                                                                         | 25<br/>▀       |
| <a href="https://github.com/Roasbeef"><img src="https://avatars.githubusercontent.com/u/998190?v=4" width="32"></a>                                                 | Roasbeef<br/>🥈   | 14<br/>▀▀      | [7d 9h 40m](https://app.flowwer.dev/charts/review-time/~(u~(i~'998190~n~'Roasbeef)~p~30~r~(~(d~'sb1622~t~'4iqv)~(d~'sb2ub3~t~'fhl)~(d~'sb4tmn~t~'zus)~(d~'sbe23m~t~'hesf)~(d~'sbe3p0~t~'27hb)~(d~'sbp6f5~t~'lcna)~(d~'sbp6i1~t~'dpiv)~(d~'sbpeoo~t~'dxpi)~(d~'sbpgfz~t~'dk8a)~(d~'sbpiwz~t~'sxsa)~(d~'sbr7wm~t~'fqxg)~(d~'sbsv06~t~'he10)~(d~'sbswl2~t~'47fz)~(d~'sbsxeh~t~'ta9)~(d~'sbuo3m~t~'irvx)~(d~'sbuo3y~t~'irw9)~(d~'sbuqxw~t~'iuq7)~(d~'sc3xzs~t~'92c)~(d~'sc3y33~t~'95n)~(d~'sccw0d~t~'5wwu)~(d~'sces9p~t~'q34m)~(d~'scfe21~t~'e5ea)~(d~'scfeuj~t~'bpx3)~(d~'scffsj~t~'8li9)~(d~'scffu1~t~'1gj3b)~(d~'scfgei~t~'qr9f)~(d~'scfhkj~t~'8gsf))))<br/>▀                                                                 | **77**<br/>▀▀▀ |
| <a href="https://github.com/ellemouton"><img src="https://avatars.githubusercontent.com/u/25280249?u=4da24e4115facc85d4f9937fe6cbcff5d3abf6e0&v=4" width="32"></a>  | ellemouton<br/>🥉 | 7<br/>▀        | [**5h 10m**](https://app.flowwer.dev/charts/review-time/~(u~(i~'25280249~n~'ellemouton)~p~30~r~(~(d~'say1de~t~'5xl)~(d~'say88r~t~'ez)~(d~'sbb0w5~t~'adrm)~(d~'sbb501~t~'12r4)~(d~'sbcwq8~t~'mqw)~(d~'sbetlv~t~'2jmj)~(d~'sbewvz~t~'16pb)~(d~'sc3oo9~t~'15l)~(d~'sc3oq8~t~'17k)~(d~'sc3osh~t~'19t))))<br/>                                                                                                                                                                                                                                                                                                                                                                                                                    | 7<br/>         |
| <a href="https://github.com/bhandras"><img src="https://avatars.githubusercontent.com/u/554360?u=e94d0ce1157aaef37a43df671cffe181a61230ad&v=4" width="32"></a>      | bhandras<br/>     | 6<br/>▀        | [13h 56m](https://app.flowwer.dev/charts/review-time/~(u~(i~'554360~n~'bhandras)~p~30~r~(~(d~'saymbs~t~'ei0)~(d~'sb2f4v~t~'bd)~(d~'sbb4xm~t~'12op)~(d~'sbtf73~t~'jsa)~(d~'sbu7hk~t~'1c2r)~(d~'sbzhnu~t~'d8bn)~(d~'sc74yr~t~'378x))))<br/>                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    | 1<br/>         |
| <a href="https://github.com/ziggie1984"><img src="https://avatars.githubusercontent.com/u/90319354?u=04ef4180951baf4d77c14f58b7c931b21ae5353c&v=4" width="32"></a>  | ziggie1984<br/>   | 6<br/>▀        | [8d 18h 41m](https://app.flowwer.dev/charts/review-time/~(u~(i~'90319354~n~'ziggie1984)~p~30~r~(~(d~'sb2pzb~t~'22us)~(d~'sb31x9~t~'6em2)~(d~'sbn22y~t~'3ump)~(d~'sbzgos~t~'g98j)~(d~'sbzxl9~t~'o1dk)~(d~'sc05el~t~'o96w)~(d~'sc05hc~t~'o99n)~(d~'sc05is~t~'o9b3)~(d~'sc0dl0~t~'ohdb)~(d~'scci4g~t~'cdyw)~(d~'scecfg~t~'1984))))<br/>▀                                                                                                                                                                                                                                                                                                                                                                                        | 45<br/>▀▀      |
| <a href="https://github.com/morehouse"><img src="https://avatars.githubusercontent.com/u/19176026?v=4" width="32"></a>                                              | morehouse<br/>    | 4<br/>▀        | [7d 11h 12m](https://app.flowwer.dev/charts/review-time/~(u~(i~'19176026~n~'morehouse)~p~30~r~(~(d~'sb0k0e~t~'3wp7)~(d~'sbzn42~t~'nqwd)~(d~'sbzz04~t~'o2sf)~(d~'sc03fv~t~'o786)~(d~'sc9l8o~t~'2g1a)~(d~'sceo54~t~'1kxs))))<br/>▀                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             | 19<br/>▀       |
| <a href="https://github.com/ProofOfKeags"><img src="https://avatars.githubusercontent.com/u/4033651?u=20a8114dedff5aa07ee208a3860147f96895d94d&v=4" width="32"></a> | ProofOfKeags<br/> | 3<br/>         | [7d 8h 56m](https://app.flowwer.dev/charts/review-time/~(u~(i~'4033651~n~'ProofOfKeags)~p~30~r~(~(d~'saz7mg~t~'2kb9)~(d~'sb127p~t~'4ewi)~(d~'sb129l~t~'4eye)~(d~'sb4hhd~t~'7u66)~(d~'sb4j2i~t~'7vrb)~(d~'sba912~t~'dlpv)~(d~'sbaap8~t~'dne1)~(d~'sbaavq~t~'dnkj)~(d~'sbab4d~t~'dnt6)~(d~'sbadia~t~'dq73)~(d~'sbc6xr~t~'fjmk)~(d~'sbc6yn~t~'fjng)~(d~'sbc7ec~t~'fk35)~(d~'sbc88l~t~'fkxe)~(d~'sbn0n6~t~'jpt)~(d~'sbn63t~t~'p6g)~(d~'sbn72k~t~'q57)~(d~'sbn77t~t~'qag)~(d~'sbn86f~t~'r92)~(d~'sbozzr~t~'2j2e)~(d~'sbulqz~t~'ipja)~(d~'sbup2u~t~'isv5)~(d~'sbv7wc~t~'8qyz)~(d~'sbzuas~t~'dddf)~(d~'sbzunn~t~'nyfy)~(d~'sbzvv3~t~'nzne)~(d~'sc051y~t~'o8u9)~(d~'sc1w8q~t~'ffbd)~(d~'sc1wc2~t~'ffep)~(d~'sc5gss~t~'izvf))))<br/>▀ | 40<br/>▀▀      |
| <a href="https://github.com/yyforyongyu"><img src="https://avatars.githubusercontent.com/u/5845072?u=75cc18e789ec4b275a32c170ca6cd5118fdfe120&v=4" width="32"></a>  | yyforyongyu<br/>  | 2<br/>         | [12h 24m](https://app.flowwer.dev/charts/review-time/~(u~(i~'5845072~n~'yyforyongyu)~p~30~r~(~(d~'sc7j8w~t~'1vp5)~(d~'scdyom~t~'16w))))<br/>                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 | 1<br/>         |
| <a href="https://github.com/Crypt-iQ"><img src="https://avatars.githubusercontent.com/u/15145615?u=a19ad52f8c597f3518a89a52014fabefda644078&v=4" width="32"></a>    | Crypt-iQ<br/>     | 2<br/>         | [7d 5h 38m](https://app.flowwer.dev/charts/review-time/~(u~(i~'15145615~n~'Crypt-iQ)~p~30~r~(~(d~'sba1mo~t~'debh)~(d~'sbbfet~t~'es3m)~(d~'scevnw~t~'b6qg))))<br/>▀                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           | 8<br/>         |
| <a href="https://github.com/saubyk"><img src="https://avatars.githubusercontent.com/u/39208279?u=393cf55b8befb6673d7c3df73e4eccd90c0fedc9&v=4" width="32"></a>      | saubyk<br/>       | 2<br/>         | [3d 17h 37m](https://app.flowwer.dev/charts/review-time/~(u~(i~'39208279~n~'saubyk)~p~30~r~(~(d~'sc02t0~t~'dtgt)~(d~'scfbkb~t~'f7))))<br/>                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   | 0<br/>         |
| <a href="https://github.com/ffranr"><img src="https://avatars.githubusercontent.com/u/5958289?u=395a874582885c25ff062733645cbb0138cf94ed&v=4" width="32"></a>       | ffranr<br/>       | 1<br/>         | [3d 16h 49m](https://app.flowwer.dev/charts/review-time/~(u~(i~'5958289~n~'ffranr)~p~30~r~(~(d~'sbh5wn~t~'13cf)~(d~'sbsomw~t~'cm2o))))<br/>                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  | 5<br/>         |
| <a href="https://github.com/dstadulis"><img src="https://avatars.githubusercontent.com/u/15150758?u=fdb1e3bb4f19269d4b3194748d45f3f906c462df&v=4" width="32"></a>   | dstadulis<br/>    | 1<br/>         | [14d 35m](https://app.flowwer.dev/charts/review-time/~(u~(i~'15150758~n~'dstadulis)~p~30~r~(~(d~'sbt1vx~t~'pyxj))))<br/>▀▀                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   | 1<br/>         |
| <a href="https://github.com/bitromortac"><img src="https://avatars.githubusercontent.com/u/36066690?u=de5285400228e4a6f5903e34825623ff3652f2e3&v=4" width="32"></a> | bitromortac<br/>  | 1<br/>         | [14d 3h 18m](https://app.flowwer.dev/charts/review-time/~(u~(i~'36066690~n~'bitromortac)~p~30~r~(~(d~'sc6olt~t~'nh5k)~(d~'scc395~t~'svsw))))<br/>▀▀                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          | 3<br/>         |
| <a href="https://github.com/Chinwendu20"><img src="https://avatars.githubusercontent.com/u/59079323?u=e690262751e589ccb4f15142c9ad71705abc074f&v=4" width="32"></a> | Chinwendu20<br/>  | 1<br/>         | [23h 27m](https://app.flowwer.dev/charts/review-time/~(u~(i~'59079323~n~'Chinwendu20)~p~30~r~(~(d~'sb2gaa~t~'1t5r))))<br/>                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   | 1<br/>         |
coderabbitai[bot]: <!-- This is an auto-generated comment: summarize by coderabbit.ai -->
<!-- This is an auto-generated comment: skip review by coderabbit.ai -->

> [!IMPORTANT]
> ## Auto Review Skipped
> 
> Auto reviews are disabled on this repository.
> 
> Please check the settings in the CodeRabbit UI or the `.coderabbit.yaml` file in this repository. To trigger a single review, invoke the `@coderabbitai review` command.
> 
> You can disable this status message by setting the `reviews.review_status` to `false` in the CodeRabbit configuration file.

<!-- end of auto-generated comment: skip review by coderabbit.ai --><!-- tips_start -->

---

Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

<details>
<summary>Share</summary>

- [X](https://twitter.com/intent/tweet?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A&url=https%3A//coderabbit.ai)
- [Mastodon](https://mastodon.social/share?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A%20https%3A%2F%2Fcoderabbit.ai)
- [Reddit](https://www.reddit.com/submit?title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&text=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code.%20Check%20it%20out%3A%20https%3A//coderabbit.ai)
- [LinkedIn](https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fcoderabbit.ai&mini=true&title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&summary=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code)

</details>

<details>
<summary>Tips</summary>

### Chat

There are 3 ways to chat with [CodeRabbit](https://coderabbit.ai):

- Review comments: Directly reply to a review comment made by CodeRabbit. Example:
	- `I pushed a fix in commit <commit_id>.`
	- `Generate unit testing code for this file.`
	- `Open a follow-up GitHub issue for this discussion.`
- Files and specific lines of code (under the "Files changed" tab): Tag `@coderabbitai` in a new review comment at the desired location with your query. Examples:
	- `@coderabbitai generate unit testing code for this file.`
	-	`@coderabbitai modularize this function.`
- PR comments: Tag `@coderabbitai` in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
	- `@coderabbitai generate interesting stats about this repository and render them as a table.`
	- `@coderabbitai show all the console.log statements in this repository.`
	- `@coderabbitai read src/utils.ts and generate unit testing code.`
	- `@coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.`

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

### CodeRabbit Commands (invoked as PR comments)

- `@coderabbitai pause` to pause the reviews on a PR.
- `@coderabbitai resume` to resume the paused reviews.
- `@coderabbitai review` to trigger a review. This is useful when automatic reviews are disabled for the repository.
- `@coderabbitai resolve` resolve all the CodeRabbit review comments.
- `@coderabbitai help` to get help.


Additionally, you can add `@coderabbitai ignore` anywhere in the PR description to prevent this PR from being reviewed.

### CodeRabbit Configration File (`.coderabbit.yaml`)

- You can programmatically configure CodeRabbit by adding a `.coderabbit.yaml` file to the root of your repository.
- Please see the [configuration documentation](https://docs.coderabbit.ai/guides/configure-coderabbit) for more information.
- If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: `# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json`

### Documentation and Community

- Visit our [Documentation](https://coderabbit.ai/docs) for detailed information on how to use CodeRabbit.
- Join our [Discord Community](https://discord.com/invite/GsXnASn26c) to get help, request features, and share feedback.
- Follow us on [X/Twitter](https://twitter.com/coderabbitai) for updates and announcements.

</details>

<!-- tips_end -->
guggero: I can't approve the PR because I re-opened it. But I reviewed it and worked my feedback directly into the commits.
So I'm approving it with this comment: LGTM :tada: 

Patch:
diff --git a/channeldb/channel.go b/channeldb/channel.go
index 18db1d207..cb2490fcd 100644
--- a/channeldb/channel.go
+++ b/channeldb/channel.go
@@ -225,27 +225,138 @@ const (
 	// A tlv type definition used to serialize an outpoint's indexStatus
 	// for use in the outpoint index.
 	indexStatusType tlv.Type = 0
+)
 
-	// A tlv type definition used to serialize and deserialize a KeyLocator
-	// from the database.
-	keyLocType tlv.Type = 1
+// chanAuxData houses the auxiliary data that is stored for each channel in a
+// TLV stream within the root bucket. This is stored as a TLV stream appended
+// to the existing hard-coded fields in the channel's root bucket.
+type chanAuxData struct {
+	// revokeKeyLoc is the key locator for the revocation key.
+	revokeKeyLoc tlv.RecordT[tlv.TlvType1, keyLocRecord]
 
-	// A tlv type used to serialize and deserialize the
-	// `InitialLocalBalance` field.
-	initialLocalBalanceType tlv.Type = 2
+	// initialLocalBalance is the initial local balance of the channel.
+	initialLocalBalance tlv.RecordT[tlv.TlvType2, uint64]
 
-	// A tlv type used to serialize and deserialize the
-	// `InitialRemoteBalance` field.
-	initialRemoteBalanceType tlv.Type = 3
+	// initialRemoteBalance is the initial remote balance of the channel.
+	initialRemoteBalance tlv.RecordT[tlv.TlvType3, uint64]
 
-	// A tlv type definition used to serialize and deserialize the
-	// confirmed ShortChannelID for a zero-conf channel.
-	realScidType tlv.Type = 4
+	// realScid is the real short channel ID of the channel corresponding to
+	// the on-chain outpoint.
+	realScid tlv.RecordT[tlv.TlvType4, lnwire.ShortChannelID]
 
-	// A tlv type definition used to serialize and deserialize the
-	// Memo for the channel channel.
-	channelMemoType tlv.Type = 5
-)
+	// memo is an optional text field that gives context to the user about
+	// the channel.
+	memo tlv.OptionalRecordT[tlv.TlvType5, []byte]
+
+	// tapscriptRoot is the optional Tapscript root the channel funding
+	// output commits to.
+	tapscriptRoot tlv.OptionalRecordT[tlv.TlvType6, [32]byte]
+}
+
+// encode serializes the chanAuxData to the given io.Writer.
+func (c *chanAuxData) encode(w io.Writer) error {
+	tlvRecords := []tlv.Record{
+		c.revokeKeyLoc.Record(),
+		c.initialLocalBalance.Record(),
+		c.initialRemoteBalance.Record(),
+		c.realScid.Record(),
+	}
+	c.memo.WhenSome(func(memo tlv.RecordT[tlv.TlvType5, []byte]) {
+		tlvRecords = append(tlvRecords, memo.Record())
+	})
+	c.tapscriptRoot.WhenSome(
+		func(root tlv.RecordT[tlv.TlvType6, [32]byte]) {
+			tlvRecords = append(tlvRecords, root.Record())
+		},
+	)
+
+	// Create the tlv stream.
+	tlvStream, err := tlv.NewStream(tlvRecords...)
+	if err != nil {
+		return err
+	}
+
+	return tlvStream.Encode(w)
+}
+
+// decode deserializes the chanAuxData from the given io.Reader.
+func (c *chanAuxData) decode(r io.Reader) error {
+	memo := c.memo.Zero()
+	tapscriptRoot := c.tapscriptRoot.Zero()
+
+	// Create the tlv stream.
+	tlvStream, err := tlv.NewStream(
+		c.revokeKeyLoc.Record(),
+		c.initialLocalBalance.Record(),
+		c.initialRemoteBalance.Record(),
+		c.realScid.Record(),
+		memo.Record(),
+		tapscriptRoot.Record(),
+	)
+	if err != nil {
+		return err
+	}
+
+	tlvs, err := tlvStream.DecodeWithParsedTypes(r)
+	if err != nil {
+		return err
+	}
+
+	if _, ok := tlvs[memo.TlvType()]; ok {
+		c.memo = tlv.SomeRecordT(memo)
+	}
+	if _, ok := tlvs[tapscriptRoot.TlvType()]; ok {
+		c.tapscriptRoot = tlv.SomeRecordT(tapscriptRoot)
+	}
+
+	return nil
+}
+
+// toOpeChan converts the chanAuxData to an OpenChannel by setting the relevant
+// fields in the OpenChannel struct.
+func (c *chanAuxData) toOpenChan(o *OpenChannel) {
+	o.RevocationKeyLocator = c.revokeKeyLoc.Val.KeyLocator
+	o.InitialLocalBalance = lnwire.MilliSatoshi(c.initialLocalBalance.Val)
+	o.InitialRemoteBalance = lnwire.MilliSatoshi(c.initialRemoteBalance.Val)
+	o.confirmedScid = c.realScid.Val
+	c.memo.WhenSomeV(func(memo []byte) {
+		o.Memo = memo
+	})
+	c.tapscriptRoot.WhenSomeV(func(h [32]byte) {
+		o.TapscriptRoot = fn.Some[chainhash.Hash](h)
+	})
+}
+
+// newChanAuxDataFromChan creates a new chanAuxData from the given channel.
+func newChanAuxDataFromChan(openChan *OpenChannel) *chanAuxData {
+	c := &chanAuxData{
+		revokeKeyLoc: tlv.NewRecordT[tlv.TlvType1](
+			keyLocRecord{openChan.RevocationKeyLocator},
+		),
+		initialLocalBalance: tlv.NewPrimitiveRecord[tlv.TlvType2](
+			uint64(openChan.InitialLocalBalance),
+		),
+		initialRemoteBalance: tlv.NewPrimitiveRecord[tlv.TlvType3](
+			uint64(openChan.InitialRemoteBalance),
+		),
+		realScid: tlv.NewRecordT[tlv.TlvType4](
+			openChan.confirmedScid,
+		),
+	}
+
+	if len(openChan.Memo) != 0 {
+		c.memo = tlv.SomeRecordT(
+			tlv.NewPrimitiveRecord[tlv.TlvType5](openChan.Memo),
+		)
+	}
+	openChan.TapscriptRoot.WhenSome(func(h chainhash.Hash) {
+		c.tapscriptRoot = tlv.SomeRecordT(
+			tlv.NewPrimitiveRecord[tlv.TlvType6, [32]byte](h),
+		)
+	})
+
+	return c
+}
 
 // indexStatus is an enum-like type that describes what state the
 // outpoint is in. Currently only two possible values.
@@ -324,6 +435,11 @@ const (
 	// SimpleTaprootFeatureBit indicates that the simple-taproot-chans
 	// feature bit was negotiated during the lifetime of the channel.
 	SimpleTaprootFeatureBit ChannelType = 1 << 10
+
+	// TapscriptRootBit indicates that this is a MuSig2 channel with a top
+	// level tapscript commitment. This MUST be set along with the
+	// SimpleTaprootFeatureBit.
+	TapscriptRootBit ChannelType = 1 << 11
 )
 
 // IsSingleFunder returns true if the channel type if one of the known single
@@ -394,6 +510,12 @@ func (c ChannelType) IsTaproot() bool {
 	return c&SimpleTaprootFeatureBit == SimpleTaprootFeatureBit
 }
 
+// HasTapscriptRoot returns true if the channel is using a top level tapscript
+// root commitment.
+func (c ChannelType) HasTapscriptRoot() bool {
+	return c&TapscriptRootBit == TapscriptRootBit
+}
+
 // ChannelConstraints represents a set of constraints meant to allow a node to
 // limit their exposure, enact flow control and ensure that all HTLCs are
 // economically relevant. This struct will be mirrored for both sides of the
@@ -856,6 +978,10 @@ type OpenChannel struct {
 	// channel that will be useful to our future selves.
 	Memo []byte
 
+	// TapscriptRoot is an optional tapscript root used to derive the MuSig2
+	// funding output.
+	TapscriptRoot fn.Option[chainhash.Hash]
+
 	// TODO(roasbeef): eww
 	Db *ChannelStateDB
 
@@ -4007,32 +4133,9 @@ func putChanInfo(chanBucket kvdb.RwBucket, channel *OpenChannel) error {
 		return err
 	}
 
-	// Convert balance fields into uint64.
-	localBalance := uint64(channel.InitialLocalBalance)
-	remoteBalance := uint64(channel.InitialRemoteBalance)
-
-	// Create the tlv stream.
-	tlvStream, err := tlv.NewStream(
-		// Write the RevocationKeyLocator as the first entry in a tlv
-		// stream.
-		MakeKeyLocRecord(
-			keyLocType, &channel.RevocationKeyLocator,
-		),
-		tlv.MakePrimitiveRecord(
-			initialLocalBalanceType, &localBalance,
-		),
-		tlv.MakePrimitiveRecord(
-			initialRemoteBalanceType, &remoteBalance,
-		),
-		MakeScidRecord(realScidType, &channel.confirmedScid),
-		tlv.MakePrimitiveRecord(channelMemoType, &channel.Memo),
-	)
-	if err != nil {
-		return err
-	}
-
-	if err := tlvStream.Encode(&w); err != nil {
-		return err
+	auxData := newChanAuxDataFromChan(channel)
+	if err := auxData.encode(&w); err != nil {
+		return fmt.Errorf("unable to encode aux data: %w", err)
 	}
 
 	if err := chanBucket.Put(chanInfoKey, w.Bytes()); err != nil {
@@ -4221,45 +4324,14 @@ func fetchChanInfo(chanBucket kvdb.RBucket, channel *OpenChannel) error {
 		}
 	}
 
-	// Create balance fields in uint64, and Memo field as byte slice.
-	var (
-		localBalance  uint64
-		remoteBalance uint64
-		memo          []byte
-	)
-
-	// Create the tlv stream.
-	tlvStream, err := tlv.NewStream(
-		// Write the RevocationKeyLocator as the first entry in a tlv
-		// stream.
-		MakeKeyLocRecord(
-			keyLocType, &channel.RevocationKeyLocator,
-		),
-		tlv.MakePrimitiveRecord(
-			initialLocalBalanceType, &localBalance,
-		),
-		tlv.MakePrimitiveRecord(
-			initialRemoteBalanceType, &remoteBalance,
-		),
-		MakeScidRecord(realScidType, &channel.confirmedScid),
-		tlv.MakePrimitiveRecord(channelMemoType, &memo),
-	)
-	if err != nil {
-		return err
+	var auxData chanAuxData
+	if err := auxData.decode(r); err != nil {
+		return fmt.Errorf("unable to decode aux data: %w", err)
 	}
 
-	if err := tlvStream.Decode(r); err != nil {
-		return err
-	}
-
-	// Attach the balance fields.
-	channel.InitialLocalBalance = lnwire.MilliSatoshi(localBalance)
-	channel.InitialRemoteBalance = lnwire.MilliSatoshi(remoteBalance)
-
-	// Attach the memo field if non-empty.
-	if len(memo) > 0 {
-		channel.Memo = memo
-	}
+	// Assign all the relevant fields from the aux data into the actual
+	// open channel.
+	auxData.toOpenChan(channel)
 
 	channel.Packager = NewChannelPackager(channel.ShortChannelID)
 
@@ -4417,6 +4489,25 @@ func deleteThawHeight(chanBucket kvdb.RwBucket) error {
 	return chanBucket.Delete(frozenChanKey)
 }
 
+// keyLocRecord is a wrapper struct around keychain.KeyLocator to implement the
+// tlv.RecordProducer interface.
+type keyLocRecord struct {
+	keychain.KeyLocator
+}
+
+// Record creates a Record out of a KeyLocator using the passed Type and the
+// EKeyLocator and DKeyLocator functions. The size will always be 8 as
+// KeyFamily is uint32 and the Index is uint32.
+//
+// NOTE: This is part of the tlv.RecordProducer interface.
+func (k *keyLocRecord) Record() tlv.Record {
+	// Note that we set the type here as zero, as when used with a
+	// tlv.RecordT, the type param will be used as the type.
+	return tlv.MakeStaticRecord(
+		0, &k.KeyLocator, 8, EKeyLocator, DKeyLocator,
+	)
+}
+
 // EKeyLocator is an encoder for keychain.KeyLocator.
 func EKeyLocator(w io.Writer, val interface{}, buf *[8]byte) error {
 	if v, ok := val.(*keychain.KeyLocator); ok {
@@ -4445,22 +4536,6 @@ func DKeyLocator(r io.Reader, val interface{}, buf *[8]byte, l uint64) error {
 	return tlv.NewTypeForDecodingErr(val, "keychain.KeyLocator", l, 8)
 }
 
-// MakeKeyLocRecord creates a Record out of a KeyLocator using the passed
-// Type and the EKeyLocator and DKeyLocator functions. The size will always be
-// 8 as KeyFamily is uint32 and the Index is uint32.
-func MakeKeyLocRecord(typ tlv.Type, keyLoc *keychain.KeyLocator) tlv.Record {
-	return tlv.MakeStaticRecord(typ, keyLoc, 8, EKeyLocator, DKeyLocator)
-}
-
-// MakeScidRecord creates a Record out of a ShortChannelID using the passed
-// Type and the EShortChannelID and DShortChannelID functions. The size will
-// always be 8 for the ShortChannelID.
-func MakeScidRecord(typ tlv.Type, scid *lnwire.ShortChannelID) tlv.Record {
-	return tlv.MakeStaticRecord(
-		typ, scid, 8, lnwire.EShortChannelID, lnwire.DShortChannelID,
-	)
-}
-
 // ShutdownInfo contains various info about the shutdown initiation of a
 // channel.
 type ShutdownInfo struct {
diff --git a/contractcourt/chain_watcher.go b/contractcourt/chain_watcher.go
index 55ac0979c..5372d8c0d 100644
--- a/contractcourt/chain_watcher.go
+++ b/contractcourt/chain_watcher.go
@@ -16,6 +16,7 @@ import (
 	"github.com/davecgh/go-spew/spew"
 	"github.com/lightningnetwork/lnd/chainntnfs"
 	"github.com/lightningnetwork/lnd/channeldb"
+	"github.com/lightningnetwork/lnd/fn"
 	"github.com/lightningnetwork/lnd/input"
 	"github.com/lightningnetwork/lnd/lnwallet"
 )
@@ -301,8 +302,11 @@ func (c *chainWatcher) Start() error {
 		err error
 	)
 	if chanState.ChanType.IsTaproot() {
+		fundingOpts := fn.MapOptionZ(
+			chanState.TapscriptRoot, lnwallet.TapscriptRootToOpt,
+		)
 		c.fundingPkScript, _, err = input.GenTaprootFundingScript(
-			localKey, remoteKey, 0,
+			localKey, remoteKey, 0, fundingOpts...,
 		)
 		if err != nil {
 			return err
diff --git a/funding/manager.go b/funding/manager.go
index e110269a7..227bb0e66 100644
--- a/funding/manager.go
+++ b/funding/manager.go
@@ -23,6 +23,7 @@ import (
 	"github.com/lightningnetwork/lnd/channeldb"
 	"github.com/lightningnetwork/lnd/channeldb/models"
 	"github.com/lightningnetwork/lnd/discovery"
+	"github.com/lightningnetwork/lnd/fn"
 	"github.com/lightningnetwork/lnd/input"
 	"github.com/lightningnetwork/lnd/keychain"
 	"github.com/lightningnetwork/lnd/labels"
@@ -2853,8 +2854,12 @@ func makeFundingScript(channel *channeldb.OpenChannel) ([]byte, error) {
 	remoteKey := channel.RemoteChanCfg.MultiSigKey.PubKey
 
 	if channel.ChanType.IsTaproot() {
+		fundingOpts := fn.MapOptionZ(
+			channel.TapscriptRoot, lnwallet.TapscriptRootToOpt,
+		)
 		pkScript, _, err := input.GenTaprootFundingScript(
 			localKey, remoteKey, int64(channel.Capacity),
+			fundingOpts...,
 		)
 		if err != nil {
 			return nil, err
diff --git a/input/script_utils.go b/input/script_utils.go
index 80997eed4..9e639bca8 100644
--- a/input/script_utils.go
+++ b/input/script_utils.go
@@ -11,8 +11,10 @@ import (
 	"github.com/btcsuite/btcd/btcec/v2/schnorr"
 	"github.com/btcsuite/btcd/btcec/v2/schnorr/musig2"
 	"github.com/btcsuite/btcd/btcutil"
+	"github.com/btcsuite/btcd/chaincfg/chainhash"
 	"github.com/btcsuite/btcd/txscript"
 	"github.com/btcsuite/btcd/wire"
+	"github.com/lightningnetwork/lnd/fn"
 	"github.com/lightningnetwork/lnd/lnutils"
 	"golang.org/x/crypto/ripemd160"
 )
@@ -197,27 +199,59 @@ func GenFundingPkScript(aPub, bPub []byte, amt int64) ([]byte, *wire.TxOut, erro
 	return witnessScript, wire.NewTxOut(amt, pkScript), nil
 }
 
+// fundingScriptOpts is a functional option that can be used to modify the way
+// the funding pkScript is created.
+type fundingScriptOpts struct {
+	tapscriptRoot fn.Option[chainhash.Hash]
+}
+
+// FundingScriptOpt is a functional option that can be used to modify the way
+// the funding script is created.
+type FundingScriptOpt func(*fundingScriptOpts)
+
+// defaultFundingScriptOpts returns a new instance of the default
+// fundingScriptOpts.
+func defaultFundingScriptOpts() *fundingScriptOpts {
+	return &fundingScriptOpts{}
+}
+
+// WithTapscriptRoot is a functional option that can be used to specify the
+// tapscript root for a MuSig2 funding output.
+func WithTapscriptRoot(root chainhash.Hash) FundingScriptOpt {
+	return func(o *fundingScriptOpts) {
+		o.tapscriptRoot = fn.Some(root)
+	}
+}
+
 // GenTaprootFundingScript constructs the taproot-native funding output that
-// uses musig2 to create a single aggregated key to anchor the channel.
+// uses MuSig2 to create a single aggregated key to anchor the channel.
 func GenTaprootFundingScript(aPub, bPub *btcec.PublicKey,
-	amt int64) ([]byte, *wire.TxOut, error) {
+	amt int64, opts ...FundingScriptOpt) ([]byte, *wire.TxOut, error) {
+
+	options := defaultFundingScriptOpts()
+	for _, optFunc := range opts {
+		optFunc(options)
+	}
+
+	muSig2Opt := musig2.WithBIP86KeyTweak()
+	options.tapscriptRoot.WhenSome(func(scriptRoot chainhash.Hash) {
+		muSig2Opt = musig2.WithTaprootKeyTweak(scriptRoot[:])
+	})
 
 	// Similar to the existing p2wsh funding script, we'll always make sure
 	// we sort the keys before any major operations. In order to ensure
 	// that there's no other way this output can be spent, we'll use a BIP
-	// 86 tweak here during aggregation.
-	//
-	// TODO(roasbeef): revisit if BIP 86 is needed here?
+	// 86 tweak here during aggregation, unless the user has explicitly
+	// specified a tapscript root.
 	combinedKey, _, _, err := musig2.AggregateKeys(
-		[]*btcec.PublicKey{aPub, bPub}, true,
-		musig2.WithBIP86KeyTweak(),
+		[]*btcec.PublicKey{aPub, bPub}, true, muSig2Opt,
 	)
 	if err != nil {
 		return nil, nil, fmt.Errorf("unable to combine keys: %w", err)
 	}
 
 	// Now that we have the combined key, we can create a taproot pkScript
-	// from this, and then make the txout given the amount.
+	// from this, and then make the txOut given the amount.
 	pkScript, err := PayToTaprootScript(combinedKey.FinalKey)
 	if err != nil {
 		return nil, nil, fmt.Errorf("unable to make taproot "+
@@ -227,7 +261,7 @@ func GenTaprootFundingScript(aPub, bPub *btcec.PublicKey,
 	txOut := wire.NewTxOut(amt, pkScript)
 
 	// For the "witness program" we just return the raw pkScript since the
-	// output we create can _only_ be spent with a musig2 signature.
+	// output we create can _only_ be spent with a MuSig2 signature.
 	return pkScript, txOut, nil
 }
 
diff --git a/lnwallet/chanfunding/canned_assembler.go b/lnwallet/chanfunding/canned_assembler.go
index 21dd47339..6b95c2bd2 100644
--- a/lnwallet/chanfunding/canned_assembler.go
+++ b/lnwallet/chanfunding/canned_assembler.go
@@ -5,7 +5,9 @@ import (
 
 	"github.com/btcsuite/btcd/btcec/v2"
 	"github.com/btcsuite/btcd/btcutil"
+	"github.com/btcsuite/btcd/chaincfg/chainhash"
 	"github.com/btcsuite/btcd/wire"
+	"github.com/lightningnetwork/lnd/fn"
 	"github.com/lightningnetwork/lnd/input"
 	"github.com/lightningnetwork/lnd/keychain"
 )
@@ -56,6 +58,14 @@ type ShimIntent struct {
 	// generate an aggregate key to use as the taproot-native multi-sig
 	// output.
 	musig2 bool
+
+	// tapscriptRoot is the root of the tapscript tree that will be used to
+	// create the funding output. This field will only be utilized if the
+	// MuSig2 flag above is set to true.
+	//
+	// TODO(roasbeef): fold above into new chan type? sum type like thing,
+	// includes the tapscript root, etc
+	tapscriptRoot fn.Option[chainhash.Hash]
 }
 
 // FundingOutput returns the witness script, and the output that creates the
@@ -73,12 +83,18 @@ func (s *ShimIntent) FundingOutput() ([]byte, *wire.TxOut, error) {
 	// If musig2 is active, then we'll return a single aggregated key
 	// rather than using the "existing" funding script.
 	if s.musig2 {
+		var scriptOpts []input.FundingScriptOpt
+		s.tapscriptRoot.WhenSome(func(root chainhash.Hash) {
+			scriptOpts = append(
+				scriptOpts, input.WithTapscriptRoot(root),
+			)
+		})
+
 		// Similar to the existing p2wsh script, we'll always ensure
 		// the keys are sorted before use.
 		return input.GenTaprootFundingScript(
-			s.localKey.PubKey,
-			s.remoteKey,
-			int64(totalAmt),
+			s.localKey.PubKey, s.remoteKey, int64(totalAmt),
+			scriptOpts...,
 		)
 	}
 
diff --git a/lnwallet/chanfunding/assembler.go b/lnwallet/chanfunding/interface.go
similarity index 96%
rename from lnwallet/chanfunding/assembler.go
rename to lnwallet/chanfunding/interface.go
index a694d2a2c..ed7197973 100644
--- a/lnwallet/chanfunding/assembler.go
+++ b/lnwallet/chanfunding/interface.go
@@ -4,9 +4,11 @@ import (
 	"time"
 
 	"github.com/btcsuite/btcd/btcutil"
+	"github.com/btcsuite/btcd/chaincfg/chainhash"
 	"github.com/btcsuite/btcd/wire"
 	"github.com/btcsuite/btcwallet/wallet"
 	"github.com/btcsuite/btcwallet/wtxmgr"
+	"github.com/lightningnetwork/lnd/fn"
 	"github.com/lightningnetwork/lnd/lnwallet/chainfee"
 )
 
@@ -119,6 +121,11 @@ type Request struct {
 	// output. By definition, this'll also use segwit v1 (taproot) for the
 	// funding output.
 	Musig2 bool
+
+	// TapscriptRoot is the root of the tapscript tree that will be used to
+	// create the funding output. This field will only be utilized if the
+	// Musig2 flag above is set to true.
+	TapscriptRoot fn.Option[chainhash.Hash]
 }
 
 // Intent is returned by an Assembler and represents the base functionality the
diff --git a/lnwallet/chanfunding/psbt_assembler.go b/lnwallet/chanfunding/psbt_assembler.go
index 885fb7b46..10bcd7015 100644
--- a/lnwallet/chanfunding/psbt_assembler.go
+++ b/lnwallet/chanfunding/psbt_assembler.go
@@ -534,6 +534,7 @@ func (p *PsbtAssembler) ProvisionChannel(req *Request) (Intent, error) {
 		ShimIntent: ShimIntent{
 			localFundingAmt: p.fundingAmt,
 			musig2:          req.Musig2,
+			tapscriptRoot:   req.TapscriptRoot,
 		},
 		State:         PsbtShimRegistered,
 		BasePsbt:      p.basePsbt,
diff --git a/lnwallet/chanfunding/wallet_assembler.go b/lnwallet/chanfunding/wallet_assembler.go
index d4ee080bc..a3ac49f3a 100644
--- a/lnwallet/chanfunding/wallet_assembler.go
+++ b/lnwallet/chanfunding/wallet_assembler.go
@@ -393,7 +393,6 @@ func (w *WalletAssembler) ProvisionChannel(r *Request) (Intent, error) {
 		// we will call the specialized coin selection function for
 		// that.
 		case r.FundUpToMaxAmt != 0 && r.MinFundAmt != 0:
-
 			// We need to ensure that manually selected coins, which
 			// are spent entirely on the channel funding, leave
 			// enough funds in the wallet to cover for a reserve.
@@ -538,6 +537,7 @@ func (w *WalletAssembler) ProvisionChannel(r *Request) (Intent, error) {
 				localFundingAmt:  localContributionAmt,
 				remoteFundingAmt: r.RemoteAmt,
 				musig2:           r.Musig2,
+				tapscriptRoot:    r.TapscriptRoot,
 			},
 			InputCoins: selectedCoins,
 			coinLeaser: w.cfg.CoinLeaser,
diff --git a/lnwallet/channel.go b/lnwallet/channel.go
index 1af0e4b5f..67bf6506c 100644
--- a/lnwallet/channel.go
+++ b/lnwallet/channel.go
@@ -26,6 +26,7 @@ import (
 	"github.com/lightningnetwork/lnd/chainntnfs"
 	"github.com/lightningnetwork/lnd/channeldb"
 	"github.com/lightningnetwork/lnd/channeldb/models"
+	"github.com/lightningnetwork/lnd/fn"
 	"github.com/lightningnetwork/lnd/input"
 	"github.com/lightningnetwork/lnd/keychain"
 	"github.com/lightningnetwork/lnd/lnwallet/chainfee"
@@ -1475,8 +1476,13 @@ func (lc *LightningChannel) createSignDesc() error {
 	remoteKey := chanState.RemoteChanCfg.MultiSigKey.PubKey
 
 	if chanState.ChanType.IsTaproot() {
+		fundingOpts := fn.MapOptionZ(
+			chanState.TapscriptRoot, TapscriptRootToOpt,
+		)
+
 		fundingPkScript, _, err = input.GenTaprootFundingScript(
 			localKey, remoteKey, int64(lc.channelState.Capacity),
+			fundingOpts...,
 		)
 		if err != nil {
 			return err
@@ -6501,11 +6507,15 @@ func (lc *LightningChannel) getSignedCommitTx() (*wire.MsgTx, error) {
 				"verification nonce: %w", err)
 		}
 
+		tapscriptTweak := fn.MapOption(TapscriptRootToTweak)(
+			lc.channelState.TapscriptRoot,
+		)
+
 		// Now that we have the local nonce, we'll re-create the musig
 		// session we had for this height.
 		musigSession := NewPartialMusigSession(
 			*localNonce, ourKey, theirKey, lc.Signer,
-			&lc.fundingOutput, LocalMusigCommit,
+			&lc.fundingOutput, LocalMusigCommit, tapscriptTweak,
 		)
 
 		var remoteSig lnwire.PartialSigWithNonce
@@ -9048,12 +9058,13 @@ func (lc *LightningChannel) InitRemoteMusigNonces(remoteNonce *musig2.Nonces,
 	// TODO(roasbeef): propagate rename of signing and verification nonces
 
 	sessionCfg := &MusigSessionCfg{
-		LocalKey:    localChanCfg.MultiSigKey,
-		RemoteKey:   remoteChanCfg.MultiSigKey,
-		LocalNonce:  *localNonce,
-		RemoteNonce: *remoteNonce,
-		Signer:      lc.Signer,
-		InputTxOut:  &lc.fundingOutput,
+		LocalKey:       localChanCfg.MultiSigKey,
+		RemoteKey:      remoteChanCfg.MultiSigKey,
+		LocalNonce:     *localNonce,
+		RemoteNonce:    *remoteNonce,
+		Signer:         lc.Signer,
+		InputTxOut:     &lc.fundingOutput,
+		TapscriptTweak: lc.channelState.TapscriptRoot,
 	}
 	lc.musigSessions = NewMusigPairSession(
 		sessionCfg,
diff --git a/lnwallet/musig_session.go b/lnwallet/musig_session.go
index ecc60d07f..fa40e6f64 100644
--- a/lnwallet/musig_session.go
+++ b/lnwallet/musig_session.go
@@ -8,8 +8,10 @@ import (
 	"github.com/btcsuite/btcd/btcec/v2"
 	"github.com/btcsuite/btcd/btcec/v2/schnorr"
 	"github.com/btcsuite/btcd/btcec/v2/schnorr/musig2"
+	"github.com/btcsuite/btcd/chaincfg/chainhash"
 	"github.com/btcsuite/btcd/txscript"
 	"github.com/btcsuite/btcd/wire"
+	"github.com/lightningnetwork/lnd/fn"
 	"github.com/lightningnetwork/lnd/input"
 	"github.com/lightningnetwork/lnd/keychain"
 	"github.com/lightningnetwork/lnd/lnwire"
@@ -37,6 +39,12 @@ var (
 	ErrSessionNotFinalized = fmt.Errorf("musig2 session not finalized")
 )
 
+// tapscriptRootToSignOpt is a function that takes a tapscript root and returns
+// a MuSig2 sign opt that'll apply the tweak when signing+verifying.
+func tapscriptRootToSignOpt(root chainhash.Hash) musig2.SignOption {
+	return musig2.WithTaprootSignTweak(root[:])
+}
+
 // MusigPartialSig is a wrapper around the base musig2.PartialSignature type
 // that also includes information about the set of nonces used, and also the
 // signer. This allows us to implement the input.Signature interface, as that
@@ -54,25 +62,30 @@ type MusigPartialSig struct {
 
 	// signerKeys is the set of public keys of all signers.
 	signerKeys []*btcec.PublicKey
+
+	// tapscriptRoot is an optional tweak, that if specified, will be used
+	// instead of the normal BIP 86 tweak when validating the signature.
+	tapscriptTweak fn.Option[chainhash.Hash]
 }
 
-// NewMusigPartialSig creates a new musig partial signature.
-func NewMusigPartialSig(sig *musig2.PartialSignature,
-	signerNonce, combinedNonce lnwire.Musig2Nonce,
-	signerKeys []*btcec.PublicKey) *MusigPartialSig {
+// NewMusigPartialSig creates a new MuSig2 partial signature.
+func NewMusigPartialSig(sig *musig2.PartialSignature, signerNonce,
+	combinedNonce lnwire.Musig2Nonce, signerKeys []*btcec.PublicKey,
+	tapscriptTweak fn.Option[chainhash.Hash]) *MusigPartialSig {
 
 	return &MusigPartialSig{
-		sig:           sig,
-		signerNonce:   signerNonce,
-		combinedNonce: combinedNonce,
-		signerKeys:    signerKeys,
+		sig:            sig,
+		signerNonce:    signerNonce,
+		combinedNonce:  combinedNonce,
+		signerKeys:     signerKeys,
+		tapscriptTweak: tapscriptTweak,
 	}
 }
 
 // FromWireSig maps a wire partial sig to this internal type that we'll use to
 // perform signature validation.
-func (p *MusigPartialSig) FromWireSig(sig *lnwire.PartialSigWithNonce,
-) *MusigPartialSig {
+func (p *MusigPartialSig) FromWireSig(
+	sig *lnwire.PartialSigWithNonce) *MusigPartialSig {
 
 	p.sig = &musig2.PartialSignature{
 		S: &sig.Sig,
@@ -135,9 +148,15 @@ func (p *MusigPartialSig) Verify(msg []byte, pub *btcec.PublicKey) bool {
 	var m [32]byte
 	copy(m[:], msg)
 
+	// If we have a tapscript tweak, then we'll use that as a tweak
+	// otherwise, we'll fall back to the normal BIP 86 sign tweak.
+	signOpts := fn.MapOption(tapscriptRootToSignOpt)(
+		p.tapscriptTweak,
+	).UnwrapOr(musig2.WithBip86SignTweak())
+
 	return p.sig.Verify(
 		p.signerNonce, p.combinedNonce, p.signerKeys, pub, m,
-		musig2.WithSortedKeys(), musig2.WithBip86SignTweak(),
+		musig2.WithSortedKeys(), signOpts,
 	)
 }
 
@@ -160,6 +179,14 @@ func (n *MusigNoncePair) String() string {
 		n.SigningNonce.PubNonce[:])
 }
 
+// TapscriptRootToTweak is a function that takes a MuSig2 taproot tweak and
+// returns the root hash of the tapscript tree.
+func muSig2TweakToRoot(tweak input.MuSig2Tweaks) chainhash.Hash {
+	var root chainhash.Hash
+	copy(root[:], tweak.TaprootTweak)
+	return root
+}
+
 // MusigSession abstracts over the details of a logical musig session. A single
 // session is used for each commitment transactions. The sessions use a JIT
 // nonce style, wherein part of the session can be created using only the
@@ -197,15 +224,20 @@ type MusigSession struct {
 	// commitType tracks if this is the session for the local or remote
 	// commitment.
 	commitType MusigCommitType
+
+	// tapscriptTweak is an optional tweak, that if specified, will be used
+	// instead of the normal BIP 86 tweak when creating the MuSig2
+	// aggregate key and session.
+	tapscriptTweak fn.Option[input.MuSig2Tweaks]
 }
 
 // NewPartialMusigSession creates a new musig2 session given only the
 // verification nonce (local nonce), and the other information that has already
 // been bound to the session.
 func NewPartialMusigSession(verificationNonce musig2.Nonces,
-	localKey, remoteKey keychain.KeyDescriptor,
-	signer input.MuSig2Signer, inputTxOut *wire.TxOut,
-	commitType MusigCommitType) *MusigSession {
+	localKey, remoteKey keychain.KeyDescriptor, signer input.MuSig2Signer,
+	inputTxOut *wire.TxOut, commitType MusigCommitType,
+	tapscriptTweak fn.Option[input.MuSig2Tweaks]) *MusigSession {
 
 	signerKeys := []*btcec.PublicKey{localKey.PubKey, remoteKey.PubKey}
 
@@ -214,13 +246,14 @@ func NewPartialMusigSession(verificationNonce musig2.Nonces,
 	}
 
 	return &MusigSession{
-		nonces:     nonces,
-		remoteKey:  remoteKey,
-		localKey:   localKey,
-		inputTxOut: inputTxOut,
-		signerKeys: signerKeys,
-		signer:     signer,
-		commitType: commitType,
+		nonces:         nonces,
+		remoteKey:      remoteKey,
+		localKey:       localKey,
+		inputTxOut:     inputTxOut,
+		signerKeys:     signerKeys,
+		signer:         signer,
+		commitType:     commitType,
+		tapscriptTweak: tapscriptTweak,
 	}
 }
 
@@ -254,9 +287,9 @@ func (m *MusigSession) FinalizeSession(signingNonce musig2.Nonces) error {
 		remoteNonce = m.nonces.SigningNonce
 	}
 
-	tweakDesc := input.MuSig2Tweaks{
+	tweakDesc := m.tapscriptTweak.UnwrapOr(input.MuSig2Tweaks{
 		TaprootBIP0086Tweak: true,
-	}
+	})
 	m.session, err = m.signer.MuSig2CreateSession(
 		input.MuSig2Version100RC2, m.localKey.KeyLocator, m.signerKeys,
 		&tweakDesc, [][musig2.PubNonceSize]byte{remoteNonce.PubNonce},
@@ -351,8 +384,11 @@ func (m *MusigSession) SignCommit(tx *wire.MsgTx) (*MusigPartialSig, error) {
 		return nil, err
 	}
 
+	tapscriptRoot := fn.MapOption(muSig2TweakToRoot)(m.tapscriptTweak)
+
 	return NewMusigPartialSig(
 		sig, m.session.PublicNonce, m.combinedNonce, m.signerKeys,
+		tapscriptRoot,
 	), nil
 }
 
@@ -364,7 +400,7 @@ func (m *MusigSession) Refresh(verificationNonce *musig2.Nonces,
 
 	return NewPartialMusigSession(
 		*verificationNonce, m.localKey, m.remoteKey, m.signer,
-		m.inputTxOut, m.commitType,
+		m.inputTxOut, m.commitType, m.tapscriptTweak,
 	), nil
 }
 
@@ -451,9 +487,11 @@ func (m *MusigSession) VerifyCommitSig(commitTx *wire.MsgTx,
 	// When we verify a commitment signature, we always assume that we're
 	// verifying a signature on our local commitment. Therefore, we'll use:
 	// their remote nonce, and also public key.
+	tapscriptRoot := fn.MapOption(muSig2TweakToRoot)(m.tapscriptTweak)
 	partialSig := NewMusigPartialSig(
 		&musig2.PartialSignature{S: &sig.Sig},
 		m.nonces.SigningNonce.PubNonce, m.combinedNonce, m.signerKeys,
+		tapscriptRoot,
 	)
 
 	// With the partial sig loaded with the proper context, we'll now
@@ -537,6 +575,10 @@ type MusigSessionCfg struct {
 	// InputTxOut is the output that we're signing for. This will be the
 	// funding input.
 	InputTxOut *wire.TxOut
+
+	// TapscriptRoot is an optional tweak that can be used to modify the
+	// MuSig2 public key used in the session.
+	TapscriptTweak fn.Option[chainhash.Hash]
 }
 
 // MusigPairSession houses the two musig2 sessions needed to do funding and
@@ -561,13 +603,14 @@ func NewMusigPairSession(cfg *MusigSessionCfg) *MusigPairSession {
 	//
 	// Both sessions will be created using only the verification nonce for
 	// the local+remote party.
+	tapscriptTweak := fn.MapOption(TapscriptRootToTweak)(cfg.TapscriptTweak)
 	localSession := NewPartialMusigSession(
-		cfg.LocalNonce, cfg.LocalKey, cfg.RemoteKey,
-		cfg.Signer, cfg.InputTxOut, LocalMusigCommit,
+		cfg.LocalNonce, cfg.LocalKey, cfg.RemoteKey, cfg.Signer,
+		cfg.InputTxOut, LocalMusigCommit, tapscriptTweak,
 	)
 	remoteSession := NewPartialMusigSession(
-		cfg.RemoteNonce, cfg.LocalKey, cfg.RemoteKey,
-		cfg.Signer, cfg.InputTxOut, RemoteMusigCommit,
+		cfg.RemoteNonce, cfg.LocalKey, cfg.RemoteKey, cfg.Signer,
+		cfg.InputTxOut, RemoteMusigCommit, tapscriptTweak,
 	)
 
 	return &MusigPairSession{
diff --git a/lnwallet/reservation.go b/lnwallet/reservation.go
index a99448768..4f0940fe9 100644
--- a/lnwallet/reservation.go
+++ b/lnwallet/reservation.go
@@ -412,6 +412,10 @@ func NewChannelReservation(capacity, localFundingAmt btcutil.Amount,
 		chanType |= channeldb.ScidAliasFeatureBit
 	}
 
+	if req.TapscriptRoot.IsSome() {
+		chanType |= channeldb.TapscriptRootBit
+	}
+
 	return &ChannelReservation{
 		ourContribution: &ChannelContribution{
 			FundingAmount: ourBalance.ToSatoshis(),
@@ -445,6 +449,7 @@ func NewChannelReservation(capacity, localFundingAmt btcutil.Amount,
 			InitialLocalBalance:  ourBalance,
 			InitialRemoteBalance: theirBalance,
 			Memo:                 req.Memo,
+			TapscriptRoot:        req.TapscriptRoot,
 		},
 		pushMSat:      req.PushMSat,
 		pendingChanID: req.PendingChanID,
diff --git a/lnwallet/wallet.go b/lnwallet/wallet.go
index 7786791f9..1e8a644db 100644
--- a/lnwallet/wallet.go
+++ b/lnwallet/wallet.go
@@ -23,6 +23,7 @@ import (
 	"github.com/btcsuite/btcwallet/wallet"
 	"github.com/davecgh/go-spew/spew"
 	"github.com/lightningnetwork/lnd/channeldb"
+	"github.com/lightningnetwork/lnd/fn"
 	"github.com/lightningnetwork/lnd/input"
 	"github.com/lightningnetwork/lnd/keychain"
 	"github.com/lightningnetwork/lnd/lnwallet/chainfee"
@@ -200,6 +201,11 @@ type InitFundingReserveMsg struct {
 	// channel that will be useful to our future selves.
 	Memo []byte
 
+	// TapscriptRoot is the root of the tapscript tree that will be used to
+	// create the funding output. This is an optional field that should
+	// only be set for taproot channels.
+	TapscriptRoot fn.Option[chainhash.Hash]
+
 	// err is a channel in which all errors will be sent across. Will be
 	// nil if this initial set is successful.
 	//
@@ -2086,8 +2092,14 @@ func (l *LightningWallet) verifyCommitSig(res *ChannelReservation,
 		// already. If we're the responder in the funding flow, we may
 		// not have generated it already.
 		if res.musigSessions == nil {
+			fundingOpts := fn.MapOptionZ(
+				res.partialState.TapscriptRoot,
+				TapscriptRootToOpt,
+			)
+
 			_, fundingOutput, err := input.GenTaprootFundingScript(
 				localKey, remoteKey, channelValue,
+				fundingOpts...,
 			)
 			if err != nil {
 				return err
@@ -2327,11 +2339,18 @@ func (l *LightningWallet) handleSingleFunderSigs(req *addSingleFunderSigsMsg) {
 		fundingTxOut         *wire.TxOut
 	)
 	if chanType.IsTaproot() {
-		fundingWitnessScript, fundingTxOut, err = input.GenTaprootFundingScript( //nolint:lll
+		fundingOpts := fn.MapOptionZ(
+			pendingReservation.partialState.TapscriptRoot,
+			TapscriptRootToOpt,
+		)
+		//nolint:lll
+		fundingWitnessScript, fundingTxOut, err = input.GenTaprootFundingScript(
 			ourKey.PubKey, theirKey.PubKey, channelValue,
+			fundingOpts...,
 		)
 	} else {
-		fundingWitnessScript, fundingTxOut, err = input.GenFundingPkScript( //nolint:lll
+		//nolint:lll
+		fundingWitnessScript, fundingTxOut, err = input.GenFundingPkScript(
 			ourKey.PubKey.SerializeCompressed(),
 			theirKey.PubKey.SerializeCompressed(), channelValue,
 		)
@@ -2445,6 +2464,20 @@ func initStateHints(commit1, commit2 *wire.MsgTx,
 	return nil
 }
 
+// TapscriptRootToOpt is a helper function that converts a tapscript root into
+// the functional option we can use to pass into GenTaprootFundingScript.
+func TapscriptRootToOpt(root chainhash.Hash) []input.FundingScriptOpt {
+	return []input.FundingScriptOpt{input.WithTapscriptRoot(root)}
+}
+
+// TapscriptRootToTweak is a helper function that converts a tapscript root
+// into a tweak that can be used with the MuSig2 API.
+func TapscriptRootToTweak(root chainhash.Hash) input.MuSig2Tweaks {
+	return input.MuSig2Tweaks{
+		TaprootTweak: root[:],
+	}
+}
+
 // ValidateChannel will attempt to fully validate a newly mined channel, given
 // its funding transaction and existing channel state. If this method returns
 // an error, then the mined channel is invalid, and shouldn't be used.
@@ -2466,8 +2499,13 @@ func (l *LightningWallet) ValidateChannel(channelState *channeldb.OpenChannel,
 	// funding transaction, and also commitment validity.
 	var fundingScript []byte
 	if channelState.ChanType.IsTaproot() {
+		fundingOpts := fn.MapOptionZ(
+			channelState.TapscriptRoot, TapscriptRootToOpt,
+		)
+
 		fundingScript, _, err = input.GenTaprootFundingScript(
 			localKey, remoteKey, int64(channel.Capacity),
+			fundingOpts...,
 		)
 		if err != nil {
 			return err
diff --git a/peer/musig_chan_closer.go b/peer/musig_chan_closer.go
index 6b05b1e62..6f69a8c5b 100644
--- a/peer/musig_chan_closer.go
+++ b/peer/musig_chan_closer.go
@@ -4,6 +4,7 @@ import (
 	"fmt"
 
 	"github.com/btcsuite/btcd/btcec/v2/schnorr/musig2"
+	"github.com/lightningnetwork/lnd/fn"
 	"github.com/lightningnetwork/lnd/input"
 	"github.com/lightningnetwork/lnd/lnwallet"
 	"github.com/lightningnetwork/lnd/lnwallet/chancloser"
@@ -43,10 +44,15 @@ func (m *MusigChanCloser) ProposalClosingOpts() (
 	}
 
 	localKey, remoteKey := m.channel.MultiSigKeys()
+
+	tapscriptTweak := fn.MapOption(lnwallet.TapscriptRootToTweak)(
+		m.channel.State().TapscriptRoot,
+	)
+
 	m.musigSession = lnwallet.NewPartialMusigSession(
 		*m.remoteNonce, localKey, remoteKey,
 		m.channel.Signer, m.channel.FundingTxOut(),
-		lnwallet.RemoteMusigCommit,
+		lnwallet.RemoteMusigCommit, tapscriptTweak,
 	)
 
 	err := m.musigSession.FinalizeSession(*m.localNonce)
diff --git a/routing/router.go b/routing/router.go
index 04502d49b..aa3201f88 100644
--- a/routing/router.go
+++ b/routing/router.go
@@ -1557,6 +1557,8 @@ func makeFundingScript(bitcoinKey1, bitcoinKey2 []byte,
 			return nil, err
 		}
 
+		// TODO(roasbeef): add tapscript root to gossip v1.5
+
 		return fundingScript, nil
 	}
 


Test patch:
diff --git a/channeldb/channel_test.go b/channeldb/channel_test.go
index 981ddf688..2389015cf 100644
--- a/channeldb/channel_test.go
+++ b/channeldb/channel_test.go
@@ -17,6 +17,7 @@ import (
 	"github.com/davecgh/go-spew/spew"
 	"github.com/lightningnetwork/lnd/channeldb/models"
 	"github.com/lightningnetwork/lnd/clock"
+	"github.com/lightningnetwork/lnd/fn"
 	"github.com/lightningnetwork/lnd/keychain"
 	"github.com/lightningnetwork/lnd/kvdb"
 	"github.com/lightningnetwork/lnd/lnmock"
@@ -172,7 +173,7 @@ func fundingPointOption(chanPoint wire.OutPoint) testChannelOption {
 }
 
 // channelIDOption is an option which sets the short channel ID of the channel.
-var channelIDOption = func(chanID lnwire.ShortChannelID) testChannelOption {
+func channelIDOption(chanID lnwire.ShortChannelID) testChannelOption {
 	return func(params *testChannelParams) {
 		params.channel.ShortChannelID = chanID
 	}
@@ -312,6 +313,9 @@ func createTestChannelState(t *testing.T, cdb *ChannelStateDB) *OpenChannel {
 	uniqueOutputIndex.Add(1)
 	op := wire.OutPoint{Hash: key, Index: uniqueOutputIndex.Load()}
 
+	var tapscriptRoot chainhash.Hash
+	copy(tapscriptRoot[:], bytes.Repeat([]byte{1}, 32))
+
 	return &OpenChannel{
 		ChanType:          SingleFunderBit | FrozenBit,
 		ChainHash:         key,
@@ -354,6 +358,8 @@ func createTestChannelState(t *testing.T, cdb *ChannelStateDB) *OpenChannel {
 		ThawHeight:              uint32(defaultPendingHeight),
 		InitialLocalBalance:     lnwire.MilliSatoshi(9000),
 		InitialRemoteBalance:    lnwire.MilliSatoshi(3000),
+		Memo:                    []byte("test"),
+		TapscriptRoot:           fn.Some(tapscriptRoot),
 	}
 }
 
diff --git a/lnwallet/chancloser/chancloser_test.go b/lnwallet/chancloser/chancloser_test.go
index 1956f0d2b..afb177f56 100644
--- a/lnwallet/chancloser/chancloser_test.go
+++ b/lnwallet/chancloser/chancloser_test.go
@@ -14,6 +14,7 @@ import (
 	"github.com/btcsuite/btcd/txscript"
 	"github.com/btcsuite/btcd/wire"
 	"github.com/lightningnetwork/lnd/channeldb"
+	"github.com/lightningnetwork/lnd/fn"
 	"github.com/lightningnetwork/lnd/input"
 	"github.com/lightningnetwork/lnd/keychain"
 	"github.com/lightningnetwork/lnd/lnutils"
@@ -175,8 +176,9 @@ func (m *mockChannel) RemoteUpfrontShutdownScript() lnwire.DeliveryAddress {
 }
 
 func (m *mockChannel) CreateCloseProposal(fee btcutil.Amount,
-	localScript, remoteScript []byte, _ ...lnwallet.ChanCloseOpt,
-) (input.Signature, *chainhash.Hash, btcutil.Amount, error) {
+	localScript, remoteScript []byte,
+	_ ...lnwallet.ChanCloseOpt) (input.Signature, *chainhash.Hash,
+	btcutil.Amount, error) {
 
 	if m.chanType.IsTaproot() {
 		return lnwallet.NewMusigPartialSig(
@@ -185,6 +187,7 @@ func (m *mockChannel) CreateCloseProposal(fee btcutil.Amount,
 				R: new(btcec.PublicKey),
 			},
 			lnwire.Musig2Nonce{}, lnwire.Musig2Nonce{}, nil,
+			fn.None[chainhash.Hash](),
 		), nil, 0, nil
 	}
 
diff --git a/lnwallet/channel_test.go b/lnwallet/channel_test.go
index 7ef5c118a..c73abdbe0 100644
--- a/lnwallet/channel_test.go
+++ b/lnwallet/channel_test.go
@@ -386,6 +386,12 @@ func TestSimpleAddSettleWorkflow(t *testing.T) {
 		)
 	})
 
+	t.Run("taproot with tapscript root", func(t *testing.T) {
+		flags := channeldb.SimpleTaprootFeatureBit |
+			channeldb.TapscriptRootBit
+		testAddSettleWorkflow(t, true, flags, false)
+	})
+
 	t.Run("storeFinalHtlcResolutions=true", func(t *testing.T) {
 		testAddSettleWorkflow(t, false, 0, true)
 	})
@@ -828,6 +834,16 @@ func TestForceClose(t *testing.T) {
 			anchorAmt:            anchorSize * 2,
 		})
 	})
+	t.Run("taproot with tapscript root", func(t *testing.T) {
+		testForceClose(t, &forceCloseTestCase{
+			chanType: channeldb.SingleFunderTweaklessBit |
+				channeldb.AnchorOutputsBit |
+				channeldb.SimpleTaprootFeatureBit |
+				channeldb.TapscriptRootBit,
+			expectedCommitWeight: input.TaprootCommitWeight,
+			anchorAmt:            anchorSize * 2,
+		})
+	})
 }
 
 type forceCloseTestCase struct {
diff --git a/lnwallet/test_utils.go b/lnwallet/test_utils.go
index e33d563c3..61ab72c20 100644
--- a/lnwallet/test_utils.go
+++ b/lnwallet/test_utils.go
@@ -14,6 +14,7 @@ import (
 	"github.com/btcsuite/btcd/chaincfg/chainhash"
 	"github.com/btcsuite/btcd/wire"
 	"github.com/lightningnetwork/lnd/channeldb"
+	"github.com/lightningnetwork/lnd/fn"
 	"github.com/lightningnetwork/lnd/input"
 	"github.com/lightningnetwork/lnd/keychain"
 	"github.com/lightningnetwork/lnd/lnwallet/chainfee"
@@ -343,6 +344,21 @@ func CreateTestChannels(t *testing.T, chanType channeldb.ChannelType,
 		Packager:                channeldb.NewChannelPackager(shortChanID),
 	}
 
+	// If the channel type has a tapscript root, then we'll also specify
+	// one here to apply to both the channels.
+	if chanType.HasTapscriptRoot() {
+		var tapscriptRoot chainhash.Hash
+		_, err := io.ReadFull(rand.Reader, tapscriptRoot[:])
+		if err != nil {
+			return nil, nil, err
+		}
+
+		someRoot := fn.Some(tapscriptRoot)
+
+		aliceChannelState.TapscriptRoot = someRoot
+		bobChannelState.TapscriptRoot = someRoot
+	}
+
 	aliceSigner := input.NewMockSigner(aliceKeys, nil)
 	bobSigner := input.NewMockSigner(bobKeys, nil)