Miniscript support in Output Descriptors (part 1) (wallet)

Host: stickies-v  -  PR author: darosior

The PR branch HEAD was ec72f35 at the time of this review club meeting.



This is a 2-part Review Club. Since we haven’t covered Miniscript before, we first take some time to get familiar with the general concepts before diving into the actual PR.

  • In the first part, we’ll disregard output descriptors and look at Miniscript in general and some of the changes this PR introduces. We’ll focus on the first 6 commits from “miniscript: remove a workaround for a GCC 4.8 bug” to “miniscript: split ValidSatisfactions from IsSane”.

  • In the second part, we’ll look at the Miniscript output descriptor implementation. We’ll focus on the last 9 commits from “miniscript: tiny doc fixups” to “qa: functional test Miniscript watchonly support”.

  • Some of the questions refer to changes introduced in the PR’s predecessor #24147 which introduced the bulk of the Miniscript logic into Bitcoin Core, so it may be helpful to review that PR too. It also contains a more detailed overview of the various PRs involved in merging Miniscript into the Bitcoin Core codebase.


  • Miniscript is a language for writing (a subset of) Bitcoin Scripts in a structured way, enabling analysis, composition, generic signing and more. It is not to be confused with the policy language on top of Miniscript which looks similar to Miniscript, but is out of scope for this PR. Andrew Poelstra has a helpful video on “Getting Started with Miniscript”.

  • Output script descriptors are strings that contain all the information necessary to allow a wallet or other program to track payments made to or spent from a particular script or set of related scripts (i.e. an address or a set of related addresses such as in an HD wallet).

  • Descriptors combine well with Miniscript in allowing a wallet to handle tracking and signing for a larger variety of scripts. Since Bitcoin Core 23.0 descriptor wallets have become the default wallet type.

  • This PR #24148 introduces watch-only support for Miniscript descriptors, extending the already existing descriptor language. You’ve probably noticed that both languages have very similar syntax; this is intentional.


  1. Did you review the PR? Concept ACK, approach ACK, tested ACK, or NACK?

  2. Which type of analysis enabled by Miniscript would be helpful for which use case or application?

  3. What would be a valid Miniscript for a spending policy that unconditionally locks the UTXO for 21 blocks, and then requires a 2-of-3 multisig from Alice, Bob, or Carol? (Note: the Miniscript homepage and have easy-to-use tooling available to construct Miniscripts)

  4. What does it mean when a node is “sane” or “valid”? Do they mean the same thing?

  5. What does it mean for an expression to be non-malleably satisfiable? After SegWit, why do we still need to worry about malleability?

  6. Why does Compare now use a non-recursive algorithm, whereas previously the Node::operator== operator was recursive? What is the largest size that queue in Compare can ever grow?

  7. How do we keep track of a Node’s type and type properties? Why don’t we just declare them as regular class members? Can we instantiate a Node with multiple type properties at once?

  8. In your own words, how does Node::TreeEvalMaybe() work?

  9. In Node::CheckTimeLocksMix(), what is the type of "k"_mst? In your own words, what does the << operator do here?

  10. Why is Node<Key> templated with Key? What type(s) do we expect Key to take?

  11. Why is the Script sizefor a multi or thresh fragment depending on the value of the k threshold?

Meeting Log

  117:00 <stickies-v> #startmeeting
  217:00 <svav> Hi
  317:00 <OliverOffing> Hi
  417:00 <theStack> hi!
  517:00 <b10c> hi
  617:00 <kouloumos> hi
  717:00 <darosior> hi
  817:00 <Franko> hi
  917:00 <michaelfolkson> hi
 1017:00 <__gotcha> hi
 1117:00 <Bayer> Hey everyone
 1217:01 <xyephy> hi
 1317:01 <stickies-v> welcome everyone! This and next week we're looking at #24148 ( which introduces Miniscript support for Output Descriptors. Today we're focusing on general Miniscript concepts, and some of the changes introduced in #24148.
 1417:01 <stickies-v> but first, do we have any first timers joining us today?
 1517:01 <__gotcha> Yup
 1617:01 <paul_c> hey guys, first timer here!
 1717:02 <svav> Where did the newcomers find out about this meeting please?
 1817:02 <stickies-v> hey __gotcha , paul_c , so glad you can join us today!
 1917:02 <paul_c> I was at BTC 2022 and attended an Open Source Stage session Gloria was a part of. I learned a lot from that panel and just wanted to reach out to say hi.
 2017:02 <stickies-v> feel free to just ask when you have questions, no need to ask for permission or anything
 2117:02 <__gotcha> @josibake_ mentioned review club during chaincode seminar
 2217:02 <larryruane> Hi
 2317:03 <stickies-v> a big thank you to the expert & PR author darosior for being here, I appreciate your help in guiding this conversation, because:
 2417:03 <svav> Ok thanks newcomers and welcome!
 2517:03 <stickies-v> disclaimer: I picked tis PR just because I'm excited about the potential Miniscript brings, but I'm still relatively new to this codebase - so please keep me honest and I welcome all of your input/corrections :-)
 2617:03 <stickies-v> that said, who got the chance to review the PR or read the notes?
 2717:04 <svav> I read the notes
 2817:04 <__gotcha> read part of the notes
 2917:04 <paul_c> Skimmed the notes and just finished watching the linked Andrew Poelstra video
 3017:04 <OliverOffing> I read the notes and reviewed ~4 of the commits
 3117:04 <theStack> read the notes, didn't review code changes in detail
 3217:04 <__gotcha> and part of the linked documentation
 3317:04 <brunoerg_> hi
 3417:05 <brunoerg_> read the notes
 3517:05 <kouloumos> read the notes, didn't review code
 3617:05 <svav> An intro to Output Descriptors
 3717:06 <__gotcha> read that one
 3817:06 <stickies-v> alright not too much code review so good to start with a few general concept questions first then, it's important to understand what miniscript is about
 3917:06 <stickies-v> (svav great to have read up on that already - next week we'll dive into output descriptors)
 4017:06 <Azor> Hi
 4117:07 <stickies-v> starting off with the first question to get creatie about use cases: which type of analysis enabled by Miniscript would be helpful for which use case or application?
 4217:08 <OliverOffing> Finding smaller scripts that are equivalent in behavior
 4317:08 <stickies-v> (or more general, if you just have a cool use case for Miniscript but don't know which type of analysis it relates to - shoot!)
 4417:08 <darosior> For instance, the analysis of the maximum witness size is helpful for "second layer" protocols to assign fee bumping reserves, since they can then estimate the worst case size of the transaction (if not signed with exotic sighash types).
 4517:09 <theStack> OliverOffing: +1, also thought about that (i think smaller is pretty much always better, in order to save fees, independently of the conrete usecase)
 4617:09 <stickies-v> OliverOffing: yeah absolutely, one of the (hand-crafted) transaction templates used on LN (I believe it's the commitment tx?) was found to be slightly suboptimal thanks to Miniscript analysis
 4717:10 <darosior> OliverOffing: in general, since Miniscript is only a subset of Script some policies tend to be more optimizable "by hand". But then you lose all the guarantees given by Miniscript for just a few witness units. :)
 4817:11 <__gotcha> dariosor: which guarantees are you referring to ?
 4917:11 <darosior> But it did happen that the policy compiler found more optimal Script (for instance IIRC in the anchor output proposal for Lightning one of the Scripts was found using the policy compiler)
 5017:11 <stickies-v> personally I think composition is really interesting, where multiple parties (e.g. in an advanced kind of multi-sig) can provide complex subexpressions without everyone having to understand the other party's spending conditions
 5117:11 <darosior> __gotcha: soundness and correctness mainly, but also malleability prevention
 5217:12 <__gotcha> darosior: what is soundness in this context ?
 5317:13 <darosior> __gotcha: to expand, Script may sometimes have surprising behaviour and a Script that look to do something might actually not behave this way in all cases
 5417:13 <darosior> __gotcha: soundness in this context means basically "you can't bypass the conditions"
 5517:13 <michaelfolkson> I like the "make sure it can only be spent with me signing" (on all possible paths)
 5617:14 <michaelfolkson> CEO always has to sign for example
 5717:14 <__gotcha> iow proper combination of "and" and "or".
 5817:14 <darosior> __gotcha: from the website, "consensus sound: It is not possible to construct a witness that is consensus valid for a Script unless the spending conditions are met. Since standardness rules permit only a subset of consensus-valid satisfactions (by definition), this property also implies standardness soundness. "
 5917:15 <darosior> __gotcha: then to not lock yourself out of your funds you also want completeness "consensus and standardness complete: Assuming the resource limits listed in the previous section are not violated and there is no timelock mixing, for every set of met conditions that are permitted by the semantics, a witness can be constructed that passes Bitcoin's
 6017:15 <darosior> consensus rules and common standardness rules."
 6117:16 <__gotcha> darosior: thx
 6217:16 <stickies-v> lots of ideas here, nice! let's move on
 6317:16 <stickies-v> what would be a valid Miniscript for a spending policy that unconditionally locks the UTXO for 21 blocks, and then requires a 2-of-3 multisig from Alice, Bob, or Carol? (See or
 6417:17 <theStack> playing around with the tool resulted in this (i've replaced the hex-strings of the keys replaced by "key_{a,b,c}"):
 6517:17 <theStack> wsh(and_v(v:multi(2, key_a, key_b, key_c), older(21)))
 6617:17 <michaelfolkson> I got and(thresh(3,pk(alice),pk(bob),pk(carol)),older(21))
 6717:17 <michaelfolkson> Oops policy
 6817:17 <michaelfolkson> and_v(and_v(v:pk(alice),and_v(v:pk(bob),v:pk(carol))),older(21))
 6917:18 <__gotcha> I was closer to theStack proposal
 7017:19 <stickies-v> michaelfolkson: it was (intentionally) a bit of a trick question, but that's *policy* you posted instead of Miniscript. We use both in the discussion here, but just wanted to highlight that there is a difference. Does everyone understand the difference?
 7117:19 <OliverOffing> I don't sorry
 7217:20 <__gotcha> policy compiles to miniscript which compiles to Script ?
 7317:20 <darosior> michaelfolkson: the Miniscript you posted is a 3-of-3 or i'm missing something?
 7417:20 <stickies-v> theStack: that seems correct, except I believe the `wsh()` wrapper is specifically for Output Descriptors, and not part of Miniscript
 7517:20 <sipa> Well, the distinction is fuzzy.
 7617:21 <michaelfolkson> Oops again
 7717:21 <sipa> I think of Miniscript's "language" mostly a project to extend the descriptor language.
 7817:21 <michaelfolkson> and_v(v:multi(2,alice,bob,carol),older(21))
 7917:21 <sipa> When all of this is done, nobody should care about the distinction anymore.
 8017:21 <michaelfolkson> Yeah theStack was right unsurprisingly :)
 8117:22 <stickies-v> sipa: that would certainly help avoid confusion! (and thank you for joining us)
 8217:22 <__gotcha> sipa: not sure I understand your last statement
 8317:22 <darosior> __gotcha: just to be clear Miniscript doesn't "compile" to Script (maybe the word works but it can lead to confusion), each Miniscript fragment maps to a specific Script
 8417:22 <ls55> Is Policy the Miniscript's language ?
 8517:22 <sipa> There will just be "the descriptor language".
 8617:22 <sipa> And "the policy language".
 8717:22 <__gotcha> darosior: better wording thanks
 8817:23 <__gotcha> ok so policy and descriptor(miniscript) will remain separated
 8917:23 <sipa> Miniscript is a project to deal with vaguely generic, composable, script.
 9017:23 <OliverOffing> is thresh(2,pk(a),pk(b),pk(c)) == multi(2,pk(a),pk(b))?
 9117:23 <sipa> Rather than just a few simple templates we have now in descriptors (pkh, multi, ...)
 9217:24 <darosior> OliverOffing: well no since in the latter there is no 'c'
 9317:24 <OliverOffing> *forgot to add  pk(c) on the right-hand side
 9417:24 <darosior> Ah
 9517:24 <darosior> OliverOffing: then yes
 9617:24 <__gotcha> ls55: afaik, no
 9717:24 <darosior> But using multi() in this case is more efficient
 9817:25 <__gotcha> darosior: is "thresh" from miniscript or policy ?
 9917:25 <OliverOffing> darosior: thanks. the compiler should be able to find the most efficient implementation though right?
10017:25 <darosior> __gotcha: both :)
10117:25 <sipa> @__gotcha Both
10217:25 <darosior> OliverOffing: yes
10317:25 <michaelfolkson> This is the policy you feed into the compiler: and(thresh(2,pk(alice),pk(bob),pk(carol)),older(21))
10417:25 <michaelfolkson> And the compiler spits out the Miniscript with multi in it
10517:26 <OliverOffing> yes, verified over there too
10617:26 <stickies-v> moving on to the next question (but always feel free to continue on previous points)
10717:26 <stickies-v> what does it mean when a node is "sane" or "valid"? Do they mean the same thing?
10817:27 <theStack> michaelfolkson: which compiler did you use? i found it interesting that your 3-of-3 multisig condition was transformed into two and_v operations
10917:27 <darosior> theStack: it was wrong, the correct one is identical to yours
11017:28 <OliverOffing> what's the definition of node? is it one atom of a miniscript expression?
11117:28 <michaelfolkson> theStack: Been playing around with both listed in the notes. But that was using
11217:28 <michaelfolkson> theStack: They should both give the same result
11317:28 <darosior> OliverOffing: a fragment, eg 'and_v', 'thresh', 'multi', etc..
11417:28 <darosior> OliverOffing: too many nodes in Bitcoin land :)
11517:28 <stickies-v> OliverOffing: a Miniscript expression is essentially a tree (see for a visual). Each fragment in the tree is a node
11617:28 <theStack> darosior: yes i'm aware. still find it interesting that this optimization happened :)
11717:29 <darosior> theStack: oh sorry i misread
11817:30 <OliverOffing> i'd guess that sane means that the arguments passed to the fragment match what the fragment type expects (in terms of number of args and types)
11917:31 <darosior> theStack: the relevant lines in the Rust compiler, i'm less familiar with the C++ one but it does have the same behaviour
12017:31 <sipa> @OliverOffing No, that's validity.
12117:32 <stickies-v> so we've established the second part of the question that they are indeed not the same :-D
12217:33 <OliverOffing> Found this on StackExchange: "We use the term valid for any correctly typed Miniscript. And we use the term safe for any sane Miniscript, ie one whose satisfaction isn't malleable, which requires a key for any spending path, etc."
12317:33 <__gotcha> where are those definitions to be found ?
12417:33 <ls55> darosior: Does the Rust version use any bindings to the C++ version or is it an entirely different implementation?
12517:33 <sipa> It's an entirely separate implementation.
12617:33 <ls55> sipa: thanks
12717:34 <sipa> There is a preliminary (but abandoned, I think) Python one too.
12817:34 <stickies-v> __gotcha: that's a very fair question, the code (especially header files) is always a good place to look for definitions and documentation etc
12917:34 <stickies-v>
13017:34 <theStack> darosior: thanks! obviously is not using the latest version of rust-miniscript; at least it doesn't transform n-of-n thresholds into and_v operations
13117:35 <michaelfolkson> uses Rust implementation and uses C++ implementation. So if they were to give different results that would be worth flagging :)
13217:35 <darosior> Hint for the question about sanity: what about this miniscript thresh(101,pk(pk_1),pk(pk_2),
13317:35 <__gotcha> what is the goal of the rust implementation ? as a reference for the C++ ?
13417:35 <darosior> Is it valid?
13517:35 <OliverOffing> Yes, valid, is it passes a "type-check"
13617:35 <sipa> michaelfolkson: There are known differences between the two compilers; they don't attempt to produce identical results.
13717:35 <stickies-v> The `Node::IsSane()` method has the docstring "Whether the apparent policy of this node matches its script semantics."
13817:35 <darosior> OliverOffing: exactly! Yet is it easily spendable?
13917:36 <sipa> __gotcha: The rust implementation was written by people who wanted a rust implementation. The C++ implementation was written by people who wanted a C++ implementation.
14017:36 <__gotcha> Does that mean that sane is more restricted than valid ?
14117:36 <OliverOffing> darosior: I guess, if you have the 101 PKs...?
14217:36 <sipa> Neither is any more a reference than the other.
14317:36 <stickies-v> __gotcha: yes it does
14417:36 <michaelfolkson> sipa: Ohh in the cases where they can't be separated? Surely if one Miniscript is superior that is a (minor) flaw of one of the compilers?
14517:36 <darosior> OliverOffing: there is a catch, see "Resource limitations" at :)
14617:37 <sipa> michaelfolkson: Neither compiler is perfect.
14717:37 <sipa> (nor do we expect them to be)
14817:37 <michaelfolkson> So if you really, really cared about most efficient you should run both compilers?
14917:37 <darosior> __gotcha: yes, also for a place about the definition see the OP of the original Miniscript PR
15017:37 <sipa> Or write it by hand :D
15117:38 <OliverOffing> darosior: I understand most resource limitation points there, but what is this one? "Anything but pk(key) (P2PK), pkh(key) (P2PKH), and multi(k,...) up to n=3 is invalid by standardness (bare)."
15217:39 <darosior> OliverOffing: it's larger than 3600 bytes
15317:39 <darosior> So it's not standard
15417:39 <sipa> Note that the C++ miniscript implementation only targets P2WSH (for now).
15517:39 <OliverOffing> darosior: oh, I missed the "(bare)" :+1: thanks
15617:40 <theStack> is there any plan to implement the equivalent of an "inline assembler" expression, e.g. something like "bare_script(OP_FOO OP_BAR...)"? (not that i can think of a good use-case, just a random thought :D)
15717:40 <stickies-v> I guess to summarize sanity, it needs to be valid, consensus and standardness-compliant (e.g. number of operations and script size), have non-malleable solutions, not mix different timelock units (block/time), and not have duplicate keys
15817:40 <sipa> theStack: That already exists; `pkh(A)` is a valid descriptor, for example. It's a bare P2PKH.
15917:41 <stickies-v> Di(I hope I got that right?)
16017:41 <sipa> theStack: Also `raw(HEX)` or `addr(ADDR)` are valid descriptors.
16117:41 <darosior> stickies-v: yep
16217:41 <sipa> There isn't one that does script assembly... could be added, but I doubt that's very useful. Being able to do fancy things through miniscript is much more usable.
16317:41 <michaelfolkson> [18:35:05] <darosior> Hint for the question about sanity: what about this miniscript thresh(101,pk(pk_1),pk(pk_2),
16417:42 <michaelfolkson> I'm not seeing why this is (in)sane
16517:42 <sipa> michaelfolkson: darosior already answered it above.
16617:42 <__gotcha> Too big ?
16717:42 <theStack> sipa: agree that the script assembly one wouldn't be of much use
16817:42 <sipa> Yeah, it's too big.
16917:42 <michaelfolkson> sipa: Oh ta
17017:43 <stickies-v> alright time for the next question, we've already spoken about malleability a bit. What does it mean for an expression to be non-malleably satisfiable? After SegWit, why do we still need to worry about malleability?
17117:43 <sipa> Good question!
17217:44 — stickies-v blushes
17317:44 <theStack> my naive answer to the second questions would be: pre-segwit spending conditions are still valid (and very likely will always be), so we can't just ignore them?
17417:44 <__gotcha> does malleability introduce fuzziness regarding fees via transaction size ?
17517:44 <sipa> theStack: In miniscript we definitely have to option to just not support certain things.
17617:44 <darosior> theStack: it's only defined under wsh() for now
17717:44 <sipa> Because miniscript is specifically only able to encode a subset of script.
17817:45 <theStack> sipa, darosior: oh, good to know!
17917:45 <sipa> And indeed, we're already talking about p2wsh only.
18017:45 <darosior> __gotcha: hehe great question, yes. It's one of the reason
18117:45 <stickies-v> from the website: "For now, Miniscript is really only designed for P2WSH and P2SH-P2WSH embedded scripts."
18217:45 <michaelfolkson> SegWit didn't resolve all forms of malleability. Just signature malleability. If there are different possible witnesses with a complex script malleability is still possible
18317:46 <sipa> Segwit also didn't remove malleability. It only made it harmless for the purpose of not breaking unbroadcast transactions.
18417:46 <sipa> But malleability has other effects, which are far less severe, but still existant.
18517:47 <stickies-v> michaelfolkson: you're right, but what's the problem with having that malleability then? why should we care?
18617:47 <sipa> Segwit transactions are no less malleable than other ones.
18717:47 <sipa> __gotcha was close.
18817:47 <darosior> __gotcha: can you think of other inconveniences of third-party malleability?
18917:48 <__gotcha> not right now
19017:48 <michaelfolkson> stickies-v: Why should we care? Hmm if a second layer protocol relied on knowing wtxid? Auditability for how Bitcoin were spent from a complex script?
19117:49 <stickies-v> oh I missed __gotcha 's answer, yes exactly having a different witness can affect transaction size, and since the absolute fee amount is fixed (that part is not malleable), that would afffect the tx's fee rate - and thus it's ability to get propagated and priority to get mined into a block, which can be problematic
19217:49 <michaelfolkson> There's a OR(A,B,C). You think it was spent using A but actually it goes on the blockchain with a spend using B
19317:49 <michaelfolkson> Yeah size and fees is the better one
19417:50 <__gotcha> stickies-v: thanks for being much more precise :-)
19517:50 <sipa> That's not malleability. Third parties can't invent a valid signature by B.
19617:50 <michaelfolkson> sipa: A,B,C are (overlapping) policies :)
19717:51 <sipa> Ah, sure, in that case.
19817:51 <darosior> What if a transaction spends a Miniscript which contains a hash using another path? It needs to "dissatisfy" this hash. Once this transaction is broadcast, what can a node on the network do if they want to be a pain?
19917:52 <__gotcha> darosior: what is "containing a hash" ?
20017:52 <darosior> __gotcha: say for instance a sha256() fragment is part of the Miniscript, but not in the branch used to spend
20117:53 <darosior> Replacing the hash dissatisfaction by any 32 bytes string will not invalidate the witness
20217:53 <__gotcha> ok
20317:53 <darosior> And then for instance the first node i'm broadcasting it to can just take my transaction and send a different version to all nodes on the network
20417:54 <__gotcha> what would be the goal in that case ?
20517:54 <OliverOffing> does "non-malleably satisfiable" perhaps mean that there's no way to use a same witness data to construct a dissatisfaction of one of the fragments?
20617:54 <__gotcha> if the witness is longer, it gets less priority
20717:54 <stickies-v> to make it specific, I think an example of a policy of which the Miniscript does not have a satisfaction that's guaranteed to be non-malleable is `or(and(older(21), pk(A)), thresh(2, pk(A), pk(B)))`
20817:55 <darosior> This will increase the bandwidth usage of compact relay for everyone, since the miner will mine a transaction that is not exactly the same as every node has in its mempool
20917:55 <__gotcha> darosior: but not in the case you just described, right ?
21017:55 <__gotcha> ok, as an attack against the network as a whole iiuc
21117:55 <darosior> OliverOffing: See "Malleability" at for a definition of a non-malleable satisfaction
21217:56 <darosior> __gotcha: yeah, as a "nuisance" more than an attack let's say
21317:56 <stickies-v> okay only a few minutes left, let's do a quick question on the code just so we can say we've covered it!
21417:56 <stickies-v> in `Node::CheckTimeLocksMix()`, what is the type of `"k"_mst`? In your own words, what does the `<<` operator do here?
21517:57 <__gotcha> stickies-v: which line ?
21617:57 <stickies-v> (link:
21717:58 <theStack> the type of `"k"_mst` is `Type`, which seems to be just a uint32_t with some helper methods on top
21817:59 <stickies-v> theStack: yes correct!
21917:59 <OliverOffing> `<<` looks like a bitwise mask/operation to me
22017:59 <sipa> @OliverOffing That's correct, but I'd say that's an implementation detail. What does it *mean* for the types involved?
22117:59 <stickies-v> `"k"_mst` is converted into a `Type` instance through the user-defined literal ( `operator"" _mst` (
22217:59 <OliverOffing> left shift probably
22317:59 <__gotcha> naive cpp newbie question, is that operator overloading ?
22418:00 <sipa> @OliverOffing No.
22518:00 <theStack> (maybe people will hate me but my personal opinion is that overloading shift operators is a terrible practice)
22618:00 <sipa> @__gotcha Yes.
22718:00 <__gotcha> theStack: totally agree
22818:00 <ls55> theStack:+1
22918:00 <stickies-v> `<<` checks that every type property that the right hand has, the left hand also has
23018:00 <sipa> @theStack Fair enough... do you have a better suggestion? :)
23118:01 <stickies-v> or to quote the docstring: "Check whether the left hand's properties are superset of the right's (= left is a subtype of right)."
23218:02 <stickies-v> thank you all for bringing your A game today. Unfortunately we're out of time for this session, but there's more Miniscript joy next week. Same place, same time! Thank you again to darosior and sipa for guiding us all through this.
23318:02 <stickies-v> #endmeeting