Subject: Re: [bitcoin-dev] BIP for OP_VAULT

From: Greg Sanders <[email-masked]>
Date: Thu, 9 Mar 2023 13:45:15 -0500

AJ,

Interesting stuff! Just a couple thoughts on these proposed opcodes, at
least one we discussed elsewhere:

1) OP_FORWARD_SELF is a JET of OP_FLU in the revaulting common case. Maybe
obvious but I missed this initially and thought it was useful to be pointed
out.

2)  Using these extended primitives, you can do rate-limiting of the two
step unvaulting, or just a single step vault by committing to the partial
values. For the single stage case it's something like:

$recovery = Same As Before

$withdrawal = <deposit-delay> OP_CSV OP_DROP <pubkey> OP_CHECKSIG OP_DUP
<V> OP_LESSTHANOREQUAL OP_VERIFY OP_FORWARD_PARTIAL OP_FORWARD_TARGET
OP_FORWARD_SELF

$withdrawal is spent by:

<self-idx> <target-idx> <spk> <0<=v<=V> <signature>

where "V"  is the max allowed withdrawal value, and "deposit-delay" the
required gap in withdrawals

Due to the OP_LEQ, you are bound to ~21 BTC in value for this operation,
but for larger vaults it's pretty trivial to add larder fixed denominations
to "peel out" value until you get to your final ~21 BTC.

This rate-limiting(in either the two-stage or one-stage scheme) can limit
the risk of theft during a watchtower outage to a constant value per utxo
per time period of watchtower failure. As we've seen in the past with LN
infrastructure, software risks are often correlated, so it's a good idea to
build in belt and suspenders where we can or at least have them available
when possible.

Cheers,
Greg

On Tue, Mar 7, 2023 at 7:45 AM Anthony Towns <[email-masked]> wrote:

> On Mon, Mar 06, 2023 at 10:25:38AM -0500, James O'Beirne via bitcoin-dev
> wrote:
> > What Greg is proposing above is to in essence TLUV-ify this proposal.
>
> FWIW, the way I'm thinking about this is that the "OP_VAULT" concept is
> introducing two things:
>
>  a) the concept of "forwarding" the input amount to specified
>     outputs in a way that elegantly allows merging/splitting
>
>  b) various restrictions on the form of the output scripts
>
> These concepts go together well, because restricting an output script is
> only an interesting thing to do if you're moving value from this input
> into it. And then it's just a matter of figuring out a nice way to pick
> opcodes that combine those two concepts in interesting ways.
>
> This is different from TLUV, in that TLUV only did part (b), and
> assumed you'd do part (a) manually somehow, eg via "OP_IN_OUT_AMOUNT"
> and arithmetic opcodes. The advantage of this new approach over that
> one is that it makes it really easy to get the logic right (I often
> forgot to include the IN_OUT_AMOUNT checks at all, for instance), and
> also makes spending multiple inputs to a single output really simple,
> something that would otherwise require kind-of gnarly logic.
>
> I think there are perhaps four opcodes that are interesting in this class:
>
>    idx sPK OP_FORWARD_TARGET
>      -- sends the value to a particular output (given by idx), and
>         requires that output have a particular scriptPubKey (given
>         by sPK).
>
>    idx [...] n script OP_FORWARD_LEAF_UPDATE
>      -- sends the value to a particular output (given by idx), and
>         requires that output to have almost the same scriptPubKey as this
>         input, _except_ that the current leaf is replaced by "script",
>         with that script prefixed by "n" pushes (of values given by [...])
>
>    idx OP_FORWARD_SELF
>      -- sends the value to a particular output (given by idx), and
>         requires that output to have the same scriptPubKey as this input
>
>    amt OP_FORWARD_PARTIAL
>      -- modifies the next OP_FORWARD_* opcode to only affect "amt",
>         rather than the entire balance. opcodes after that affect the
>         remaining balance, after "amt" has been subtracted. if "amt" is
>         0, the next OP_FORWARD_* becomes a no-op.
>
> Then each time you see OP_FORWARD_TARGET or OP_FORWARD_LEAF_UPDATE, you
> accumulate the value that's expected to be forwarded to the output by
> each input, and verify that the amount for that output is greater-or-equal
> to the accumulated value.
>
> > ## Required opcodes
> > - OP_VAULT: spent to trigger withdrawal
> > - OP_VAULT_RECOVER: spent to recover
>
> Naming here is OP_VAULT ~= OP_FORWARD_LEAF_UPDATE; OP_VAULT_RECOVER ~=
> OP_FORWARD_TARGET.
>
> > For each vault, vaulted coins are spent to an output with the taproot
> > structure
> >
> >   taproot(internal_key, {$recovery_leaf, $trigger_leaf, ...})
> >
> > where
> >
> >   $trigger_leaf =
> >     <trigger> <auth> <script> <spend-delay> OP_VAULT
>
> With the opcodes above, the trigger_leaf (for spending some of the
> vaulted funds via your hot wallet) script might look like:
>
>    OP_FORWARD_PARTIAL OP_FORWARD_SELF
>    1 "288 OP_CSV OP_DROP OP_CTV" OP_FORWARD_LEAF_UPDATE
>    key CHECKSIG
>
> So if you have 2.0 BTC in a vault utxo, you might spend 0.4 BTC by
> supplying the witness data:
>
>   160000000  (1.6BTC in sats)
>   0 (output 0 puts 1.6BTC goes back into the vault)
>   <ctvhash> (the outputs where you want the remaining 0.4 BTC to end up)
>   1 (the output idx that will be spend via the CTV path once the CSV
>      delay is done)
>   <sig> (a signature of this transaction via the hot wallet "key")
>
> That is, the script becomes:
>
>   160000000 FORWARD_PARTIAL
>   0 FORWARD_SELF
>   1 <ctvhash> 1 "288 CSV DROP CTV" FORWARD_LEAF_UPDATE
>   sig key CHECKSIG
>
> Output 1 would then have a tapscript of "<ctvhash> 288 OP_CSV OP_DROP
> OP_CTV", satisfied with an empty witness stack (along with the recovery
> path, etc).
>
> Output 0 is just 1.6BTC back in your vault, and immediately available
> for use.
>
> Other inputs/outputs (for fees etc) would still be committed to by <sig>,
> so nothing here is malleable. The script here is about 45 bytes (compared
> to 34 for a simple "key CHECKSIG") and the witness data is about 105 bytes
> (compared to 65 bytes for just a signature), which seems pretty nice.
>
> >   ... =
> >     other (optional) leaves in the taptree
>
> This would allow you to have multiple hot wallets (if any of them are
> compromised you can still use the recovery path to avoid loss of funds;
> but if some hot wallet becomes temporarily *inaccessible* you can still
> easily spend the funds via one of the alternative hot wallets), or,
> if you have multiple watchtowers validating your spends and recovering
> funds to your cold wallet on a violation, you could have multiple recovery
> paths to provide some auditability for who triggered the recovery.
>
> > Happens via script-path spend to $expr_withdraw, i.e. a timelocked
> > OP_CTV.
>
> Note that if you calculated the OP_CTV incorrectly (eg, you don't set a
> correct nSequence timelock, so that any tx that passes OP_CTV won't pass
> the OP_CSV check, and vice-versa) then this spend path becomes invalid,
> and the funds can only be reclaimed via some other path (key path spend,
> recovery tapscript, potentially an alternative hotwallet script path).
>
> OP_FORWARD_LEAF_UPDATE is equivalent to a very specific form of TLUV,
> namely "FALSE <h> 2 TLUV", where "<h>" is calculated by building the
> script, prefixing the pushes, then doing the Hash_TapLeaf calculation.
>
> Not being able to tweak the internal public key ("FALSE" rather than
> "<x>") means this can't be used to build a coinpool with unilateral
> exit -- you can't remove your key from the key path, which screws over
> everyone who's still in the coinpool.
>
> On the other hand, not tweaking the internal public key avoids introducing
> all the x-only pubkey complications, and keeps it relatively simple,
> which is nice, and keeping things simple and targeted now means there's
> still plenty of OP_SUCCESS opcodes available later for something more
> general, if that turns out to be desirable.
>
> Cheers,
> aj
>