Re: OP_VAULT: a new vault proposal

Re: OP_VAULT: a new vault proposal
2023-01-09 19:02 ` rot13maxi
                   ` (2 more replies)
  
From: James O'Beirne @ 2023-01-09 16:07 UTC (permalink / raw)
  To: Bitcoin Protocol Discussion

[-- Attachment #1: Type: text/plain, Size: 2196 bytes --]

For the last few years, I've been interested in vaults as a way to
substantially derisk custodying Bitcoin, both at personal and commercial
scales. Instead of abating with familiarity, as enthusiasm sometimes
does, my conviction that vaults are an almost necessary part of bitcoin's
viability has only grown over the years.

Since people first started discussing vaults, it's been pretty clear that
some kind of covenant-enabling consensus functionality is necessary to
provide the feature set necessary to make vault use practical.

Earlier last year I experimented with using OP_CTV[1], a limited covenant
mechanism, to implement a "minimum-viable" vault design. I found that the
inherent limitations of a precomputed covenant scheme left the resulting
vault implementation wanting, even though it was an improvement over
existing strategies that rely on presigned transactions and (hopefully)
ephemeral keys.

But I also found proposed "general" covenant schemes to be
unsuitable for this use. The bloated scriptPubKeys, both in size and
complexity, that would result when implementing something like a vault
weren't encouraging. Also importantly, the social-consensus quagmire
regarding which covenant proposal to actually deploy feels at times
intractable.

As a result, I wanted to explore a middle way: a design solely concerned
with making the best vault use possible, with covenant functionality as a
secondary consideration. In other words, a proposal that would deliver
the safety benefits of vaults to users without getting hung up on
trying to solve the general problem of covenants.

At first this design, OP_VAULT, was just sort of a pipe dream. But as I
did more thinking (and eventually implementing) I became more convinced
that, even if it isn't considered for soft-fork, it is a worthwhile
device to serve as a standard benchmark against which other proposals
might be judged.

I wrote a paper that summarizes my findings and the resulting proposal:
https://jameso.be/vaults.pdf

along with an accompanying draft implementation:
https://github.com/bitcoin/bitcoin/pull/26857

I might work on a BIP if there's interest.

James

[1]: https://github.com/jamesob/simple-ctv-vault

[-- Attachment #2: Type: text/html, Size: 2587 bytes --]

 permalink raw reply	[|nested]
2023-01-09 16:07 James O'Beirne

  2023-01-09 19:31   ` Greg Sanders
  2023-01-10 12:29 ` Anthony Towns
  2023-01-10 14:17 ` James O'Beirne
  
From: rot13maxi @ 2023-01-09 19:02 UTC (permalink / raw)
  To: James O'Beirne, Bitcoin Protocol Discussion

[-- Attachment #1: Type: text/plain, Size: 3923 bytes --]

Hey James,

Really cool proposal. I’ve been thinking a lot lately about script paths for inheritance. In a lot of the “have a relative time lock that allows a different key to spend coins, or allows a smaller threshold of a multisig to spend” schemes, you have the problem of needing to “refresh” all of your coins when the timelock is close to maturation. In a lot of the “use multisig with ephemeral keys to emulate covenants” schemes, you have to pre-commit to the terminal destination well in advance of the spend-path being used, which leads to all kinds of thorny questions about security and availability of *those* keys. In other words, you either have to have unbound destinations but a timer that needs resetting, or you have unbound time but fixed destinations. This design gets you the best of both because the destination SPKs aren’t committed to until the unvaulting process starts. This (or something like this with destination binding at unvault-time) would be an incredibly useful tool for inheritance designs in wallets.

I need to think a bit more about the recovery path not having any real encumbrances on it. Maybe in practice if you’re worried about DoS, you have UTXOs that commit to multiple vault paths that have tweaked recovery destinations or something, or maybe it really is the right move to say that if recovery is triggered, you probably do want it for all of your inflight unvaultings.

Looking forward to reading this a few more times and talking more about it.

Thanks!
rijndael

On Mon, Jan 9, 2023 at 11:07 AM, James O'Beirne via bitcoin-dev <bitcoin-dev@lists•linuxfoundation.org> wrote:

> For the last few years, I've been interested in vaults as a way to
> substantially derisk custodying Bitcoin, both at personal and commercial
> scales. Instead of abating with familiarity, as enthusiasm sometimes
> does, my conviction that vaults are an almost necessary part of bitcoin's
> viability has only grown over the years.
>
> Since people first started discussing vaults, it's been pretty clear that
> some kind of covenant-enabling consensus functionality is necessary to
> provide the feature set necessary to make vault use practical.
>
> Earlier last year I experimented with using OP_CTV[1], a limited covenant
> mechanism, to implement a "minimum-viable" vault design. I found that the
> inherent limitations of a precomputed covenant scheme left the resulting
> vault implementation wanting, even though it was an improvement over
> existing strategies that rely on presigned transactions and (hopefully)
> ephemeral keys.
>
> But I also found proposed "general" covenant schemes to be
> unsuitable for this use. The bloated scriptPubKeys, both in size and
> complexity, that would result when implementing something like a vault
> weren't encouraging. Also importantly, the social-consensus quagmire
> regarding which covenant proposal to actually deploy feels at times
> intractable.
>
> As a result, I wanted to explore a middle way: a design solely concerned
> with making the best vault use possible, with covenant functionality as a
> secondary consideration. In other words, a proposal that would deliver
> the safety benefits of vaults to users without getting hung up on
> trying to solve the general problem of covenants.
>
> At first this design, OP_VAULT, was just sort of a pipe dream. But as I
> did more thinking (and eventually implementing) I became more convinced
> that, even if it isn't considered for soft-fork, it is a worthwhile
> device to serve as a standard benchmark against which other proposals
> might be judged.
>
> I wrote a paper that summarizes my findings and the resulting proposal:
> https://jameso.be/vaults.pdf
>
> along with an accompanying draft implementation:
> https://github.com/bitcoin/bitcoin/pull/26857
>
> I might work on a BIP if there's interest.
>
> James
> [1]: https://github.com/jamesob/simple-ctv-vault

[-- Attachment #2: Type: text/html, Size: 4529 bytes --]

 permalink raw reply	[|nested]
2023-01-09 19:02 ` rot13maxi

  2023-01-09 20:32     ` James O'Beirne
  
From: Greg Sanders @ 2023-01-09 19:31 UTC (permalink / raw)
  To: rot13maxi, Bitcoin Protocol Discussion

[-- Attachment #1: Type: text/plain, Size: 5147 bytes --]

Hi James and co,

Currently there is no way to make this compatible with scripthashes of any
kind, since the script interpreter has no insight into the OP_UNVAULT
outputs' "execution script", and one of the arguments of OP_UNVAULT is
freeform, resulting in an unpredictable output scriptpubkey.

I think the fix is just requiring a single additional witness data item
during OP_VAULT spend(for unvault path), mandating the
<target-outputs-hash> to be included in the witness stack as an input to
OP_VAULT opcode, and transaction introspection then checks to make sure the
witness item and the corresponding output script template matches the
expected.

This would only be necessary for the unvaulting path, and not for the
recovery path.

Cheers,
Greg

On Mon, Jan 9, 2023 at 2:10 PM rot13maxi via bitcoin-dev <
bitcoin-dev@lists•linuxfoundation.org> wrote:

> Hey James,
>
> Really cool proposal. I’ve been thinking a lot lately about script paths
> for inheritance. In a lot of the “have a relative time lock that allows a
> different key to spend coins, or allows a smaller threshold of a multisig
> to spend” schemes, you have the problem of needing to “refresh” all of your
> coins when the timelock is close to maturation. In a lot of the “use
> multisig with ephemeral keys to emulate covenants” schemes, you have to
> pre-commit to the terminal destination well in advance of the spend-path
> being used, which leads to all kinds of thorny questions about security and
> availability of *those* keys. In other words, you either have to have
> unbound destinations but a timer that needs resetting, or you have unbound
> time but fixed destinations. This design gets you the best of both because
> the destination SPKs aren’t committed to until the unvaulting process
> starts. This (or something like this with destination binding at
> unvault-time) would be an incredibly useful tool for inheritance designs in
> wallets.
>
> I need to think a bit more about the recovery path not having any real
> encumbrances on it. Maybe in practice if you’re worried about DoS, you have
> UTXOs that commit to multiple vault paths that have tweaked recovery
> destinations or something, or maybe it really is the right move to say that
> if recovery is triggered, you probably do want it for all of your inflight
> unvaultings.
>
> Looking forward to reading this a few more times and talking more about
> it.
>
> Thanks!
> rijndael
>
>
> On Mon, Jan 9, 2023 at 11:07 AM, James O'Beirne via bitcoin-dev <
> bitcoin-dev@lists•linuxfoundation.org> wrote:
>
> For the last few years, I've been interested in vaults as a way to
> substantially derisk custodying Bitcoin, both at personal and commercial
> scales. Instead of abating with familiarity, as enthusiasm sometimes
> does, my conviction that vaults are an almost necessary part of bitcoin's
> viability has only grown over the years.
>
> Since people first started discussing vaults, it's been pretty clear that
> some kind of covenant-enabling consensus functionality is necessary to
> provide the feature set necessary to make vault use practical.
>
> Earlier last year I experimented with using OP_CTV[1], a limited covenant
> mechanism, to implement a "minimum-viable" vault design. I found that the
> inherent limitations of a precomputed covenant scheme left the resulting
> vault implementation wanting, even though it was an improvement over
> existing strategies that rely on presigned transactions and (hopefully)
> ephemeral keys.
>
> But I also found proposed "general" covenant schemes to be
> unsuitable for this use. The bloated scriptPubKeys, both in size and
> complexity, that would result when implementing something like a vault
> weren't encouraging. Also importantly, the social-consensus quagmire
> regarding which covenant proposal to actually deploy feels at times
> intractable.
>
> As a result, I wanted to explore a middle way: a design solely concerned
> with making the best vault use possible, with covenant functionality as a
> secondary consideration. In other words, a proposal that would deliver
> the safety benefits of vaults to users without getting hung up on
> trying to solve the general problem of covenants.
>
> At first this design, OP_VAULT, was just sort of a pipe dream. But as I
> did more thinking (and eventually implementing) I became more convinced
> that, even if it isn't considered for soft-fork, it is a worthwhile
> device to serve as a standard benchmark against which other proposals
> might be judged.
>
> I wrote a paper that summarizes my findings and the resulting proposal:
> https://jameso.be/vaults.pdf
>
> along with an accompanying draft implementation:
> https://github.com/bitcoin/bitcoin/pull/26857
>
> I might work on a BIP if there's interest.
>
> James
>
> [1]: https://github.com/jamesob/simple-ctv-vault
>
> _______________________________________________
> bitcoin-dev mailing list
> bitcoin-dev@lists•linuxfoundation.org
> https://lists.linuxfoundation.org/mailman/listinfo/bitcoin-dev
>

[-- Attachment #2: Type: text/html, Size: 6228 bytes --]

 permalink raw reply	[|nested]
2023-01-09 19:31   ` Greg Sanders

  2023-01-10 13:35       ` James O'Beirne
  
From: James O'Beirne @ 2023-01-09 20:32 UTC (permalink / raw)
  To: Greg Sanders;  Bitcoin Protocol Discussion

[-- Attachment #1: Type: text/plain, Size: 6304 bytes --]

Hey Greg,

I think what you're trying to get at here is that the OP_UNVAULT
scriptPubKey *must* be a bare script so that the OP_VAULT spend logic can
verify that we're spending an OP_VAULT output into a compatible OP_UNVAULT
output, and that's true. The OP_UNVAULT scriptPubKey also must contain the
target hash because that has is used when validating that spend to ensure
that the final unvault target matches what was advertised when the
OP_UNVAULT output was created.

So I'm not sure what problem you're trying to solve by putting the target
hash  on the OP_VAULT spend witness stack. If it were placed there, it
wouldn't be accessible during OP_UNVAULT spend AFAICT. I agree it would be
nice to figure out a way to allow the OP_UNVAULT scriptPubKey to not be
bare, which may require moving the target hash out of it, but we'd have to
figure out a mechanism to properly forward the target hash for validation.

Best,
James

On Mon, Jan 9, 2023 at 2:32 PM Greg Sanders <gsanders87@gmail•com> wrote:

> Hi James and co,
>
> Currently there is no way to make this compatible with scripthashes of any
> kind, since the script interpreter has no insight into the OP_UNVAULT
> outputs' "execution script", and one of the arguments of OP_UNVAULT is
> freeform, resulting in an unpredictable output scriptpubkey.
>
> I think the fix is just requiring a single additional witness data item
> during OP_VAULT spend(for unvault path), mandating the
> <target-outputs-hash> to be included in the witness stack as an input to
> OP_VAULT opcode, and transaction introspection then checks to make sure the
> witness item and the corresponding output script template matches the
> expected.
>
> This would only be necessary for the unvaulting path, and not for the
> recovery path.
>
> Cheers,
> Greg
>
> On Mon, Jan 9, 2023 at 2:10 PM rot13maxi via bitcoin-dev <
> bitcoin-dev@lists•linuxfoundation.org> wrote:
>
>> Hey James,
>>
>> Really cool proposal. I’ve been thinking a lot lately about script paths
>> for inheritance. In a lot of the “have a relative time lock that allows a
>> different key to spend coins, or allows a smaller threshold of a multisig
>> to spend” schemes, you have the problem of needing to “refresh” all of your
>> coins when the timelock is close to maturation. In a lot of the “use
>> multisig with ephemeral keys to emulate covenants” schemes, you have to
>> pre-commit to the terminal destination well in advance of the spend-path
>> being used, which leads to all kinds of thorny questions about security and
>> availability of *those* keys. In other words, you either have to have
>> unbound destinations but a timer that needs resetting, or you have unbound
>> time but fixed destinations. This design gets you the best of both because
>> the destination SPKs aren’t committed to until the unvaulting process
>> starts. This (or something like this with destination binding at
>> unvault-time) would be an incredibly useful tool for inheritance designs in
>> wallets.
>>
>> I need to think a bit more about the recovery path not having any real
>> encumbrances on it. Maybe in practice if you’re worried about DoS, you have
>> UTXOs that commit to multiple vault paths that have tweaked recovery
>> destinations or something, or maybe it really is the right move to say that
>> if recovery is triggered, you probably do want it for all of your inflight
>> unvaultings.
>>
>> Looking forward to reading this a few more times and talking more about
>> it.
>>
>> Thanks!
>> rijndael
>>
>>
>> On Mon, Jan 9, 2023 at 11:07 AM, James O'Beirne via bitcoin-dev <
>> bitcoin-dev@lists•linuxfoundation.org> wrote:
>>
>> For the last few years, I've been interested in vaults as a way to
>> substantially derisk custodying Bitcoin, both at personal and commercial
>> scales. Instead of abating with familiarity, as enthusiasm sometimes
>> does, my conviction that vaults are an almost necessary part of bitcoin's
>> viability has only grown over the years.
>>
>> Since people first started discussing vaults, it's been pretty clear that
>> some kind of covenant-enabling consensus functionality is necessary to
>> provide the feature set necessary to make vault use practical.
>>
>> Earlier last year I experimented with using OP_CTV[1], a limited covenant
>> mechanism, to implement a "minimum-viable" vault design. I found that the
>> inherent limitations of a precomputed covenant scheme left the resulting
>> vault implementation wanting, even though it was an improvement over
>> existing strategies that rely on presigned transactions and (hopefully)
>> ephemeral keys.
>>
>> But I also found proposed "general" covenant schemes to be
>> unsuitable for this use. The bloated scriptPubKeys, both in size and
>> complexity, that would result when implementing something like a vault
>> weren't encouraging. Also importantly, the social-consensus quagmire
>> regarding which covenant proposal to actually deploy feels at times
>> intractable.
>>
>> As a result, I wanted to explore a middle way: a design solely concerned
>> with making the best vault use possible, with covenant functionality as a
>> secondary consideration. In other words, a proposal that would deliver
>> the safety benefits of vaults to users without getting hung up on
>> trying to solve the general problem of covenants.
>>
>> At first this design, OP_VAULT, was just sort of a pipe dream. But as I
>> did more thinking (and eventually implementing) I became more convinced
>> that, even if it isn't considered for soft-fork, it is a worthwhile
>> device to serve as a standard benchmark against which other proposals
>> might be judged.
>>
>> I wrote a paper that summarizes my findings and the resulting proposal:
>> https://jameso.be/vaults.pdf
>>
>> along with an accompanying draft implementation:
>> https://github.com/bitcoin/bitcoin/pull/26857
>>
>> I might work on a BIP if there's interest.
>>
>> James
>>
>> [1]: https://github.com/jamesob/simple-ctv-vault
>>
>> _______________________________________________
>> bitcoin-dev mailing list
>> bitcoin-dev@lists•linuxfoundation.org
>> https://lists.linuxfoundation.org/mailman/listinfo/bitcoin-dev
>>
>

[-- Attachment #2: Type: text/html, Size: 7654 bytes --]

 permalink raw reply	[|nested]
2023-01-09 16:07 James O'Beirne
  2023-01-09 19:02 ` rot13maxi

  2023-01-10 20:22   ` James O'Beirne
  2023-01-10 14:17 ` James O'Beirne
  
From: Anthony Towns @ 2023-01-10 12:29 UTC (permalink / raw)
  To: James O'Beirne, Bitcoin Protocol Discussion

On Mon, Jan 09, 2023 at 11:07:54AM -0500, James O'Beirne via bitcoin-dev wrote:
> But I also found proposed "general" covenant schemes to be
> unsuitable for this use. The bloated scriptPubKeys,

I don't think that makes sense? With a general scheme, you'd only be
bloating the witness data (perhaps including the witness script) not
the scriptPubKey?



Terminology suggestion: instead of calling it the "recovery" path,
call it "freezing your funds". Then you're using your "hot wallet" (aka
unvault-spk-hash) for the unvault path for all your normal transactions,
but if there's a problem, you freeze your funds, and they're now only
accessible via your "cold wallet" (aka recovery-spk-hash).



As I understand it, your scheme is:

  scriptPubKey: <vault tag> <#recovery> (<delay> <#unvault>)

where #recovery is the sha256 hashes of an arbitrary scriptPubKey,
#unvault is the sha256 hash of a witness script, and delay is a relative
block count.

This scriptPubKey allows for two spend paths:

  recovery: spends directly to <recovery>; verified by checking it
    the hash of the sPK matches <#recovery> and the amount it preserved

  unvaulting:
    spends to a scriptPubKey of <unvault tag> <#recovery> (<delay> <#target>)
    verified by checking that the witness script hashes to #unvault and
    is satisfied, and #target is a CTV-ish commitment of the eventual
    withdrawal (any CHECKSIG operations in the unvault witness script will
    commit to the sPK output, preventing this from being modified by
    third parties). #recovery and delay must match the values from the
    vault scriptPubKey.

The unvault scriptPubKey likewise likewise allows for two spend paths:

  recovery: same as above

  withdrawal:
    verifies that all the outputs hash to #target, and that nSequence
    is set to a relative timelock of at least delay.



This means that as soon as your recovery address (the preimage to
#recovery) is revealed, anyone can move all your funds into cold storage
(presuming they're willing to pay the going feerate to do so). I think
this is a feature, not a bug, though: if your hot wallet is compromised,
moving all your funds to cold storage is desirable; and if you want to
have different hot wallets with a single cold wallet, then you can use
a HD cold-wallet so that revealing one address corresponding to one hot
wallet, doesn't reveal the addresses corresponding to other hot wallets.
(This is addressed in the "Denial-of-service protection" section)

It does however mean that the public key for your cold wallet needs to
be handled secretly though -- if you take the cold wallet xpub and send
it to a random electrum server to check your cold wallet balance, that
would allow a malicious party to lock up all your funds.

I think it might be better to use a pay-to-contract construction for
the recovery path, rather than an empty witness. That is, take your
recovery address R, and calculate #recovery=sha256(R, sha256(secret))
(where "secret" is something derived from the R's private key, so that
it can be easily recovered if you only have your cold wallet and lose
all your metadata). When you want to recover all your funds to address R,
you reveal sha256(secret) in the witness data and R in the scriptPubKey,
OP_VAULT hashes these together and checks the result matches #recovery,
and only then allows it. That would allow you to treat R as public
knowledge, without risking your funds getting randomly frozen.



This construct allows delayed withdrawals (ie "the cold wallet can
withdraw instantly, the hot wallet can withdraw only after delay blocks"),
but I don't think it provides any way to cap withdrawals ("the cold
wallet can withdraw all funds, the hot wallet can only withdraw up to
X funds per day/week").  Having a fixed limit probably isn't compatible
with having a multi-utxo vault ("you can withdraw $10k per day" doesn't
help if your $5M is split across 500 $10k utxos, and the limit is only
enforced per-utxo), but I think a percentage limit would be.



I think a generic OP_UNVAULT can be used to simulate OP_CTV: replace
"<h> OP_CTV" with "<000..0> 0 <h> OP_UNVAULT". The paper seems to put
"OP_UNVAULT" first, but the code seems to expect it to come last, not
sure what's up with that inconsistency.

I'm not sure why you'd want a generic opcode though; if you want the
data to be visible in the scriptPubKey, you need to use a new segwit
version with structured data, anyway; so why not just do that?



I think there's maybe a cleverer way of batching / generalising checking
that input/output amounts match. That is, rather than just checking that
"the input's a vault; so the corresponding output must be one of these
possibilities, and the input/output values must exactly match", that
it's generalised to be:

  * set A = the sum of each input that's taking the unvaulting path 
    from a vault scriptPubKey with #recovery=X
  * set B = the sum of each output that has an unvault tag with
    #recovery=X
  * set C = the sum of each output that has a vault tag with
    #recovery=X
  * check that A=B+C

(That allows consolidation of your vault via your hot wallet, just by not
having any unvault outputs, so B=0. I suspect that if you allowed for
keyless consolidation of your vault, that that would be a griefing/DoS
vector)

This differs from the actual proposal, AIUI, which instead requires that
there are just two outputs - an ephemeral anchor for attaching fees, and
the primary vault or unvault output, and that all the inputs are
vaulting/unvaulting txs.

I think one meaningful difference between these two approaches is that
the current proposal means unvaulting locks up the entire utxo for the
delay period, rather than just the amount you're trying to unvault. eg,
if you have a single vault utxo X with 1000 BTC, you have delay set to
1008 blocks (1 week), and you decide on Tuesday that you wish to withdraw
50 BTC, creating an unvault tx spending 50 BTC somewhere and 950 BTC back
to your vault, you can't spend any of the 950 BTC for another two weeks:
one week for the unvault to confirm and it to go back into your vault,
and another week for the next unvault to confirm.

Changing the unvault construction to have an optional OP_VAULT output
would remedy that, I think.



It would be fairly dangerous to combine a construction like this (which
encourages the vault sPK to be reused) with APO signatures on the hot
wallet -- in that case the signature could just be replayed against a
different vault utxo, and you'd be paying for things twice. But provided
that vault spends are only (1) signed by the hot wallet, or (2) being
frozen and moved to the recovery sPK, then you should have complete
control over your utxos/coins, and using APO probably isn't interesting
anyway.



What would it look like to just hide all this under taproot?

First you'd just leave the internal pubkey as your cold wallet key
(or a NUMS point if your cold wallet is complicated).

Working backwards, your unvault output needs two script paths:

  1) move_funds_to(recovery-spk)
  2) <D> OP_CSV; move_funds_to(X)

Your vault output also needs two paths:

  1) move_funds_to(recovery-spk)
  2) hot-wallet-script; move_funds_to(unvault[X])

That obviously requires a "move_funds_to" operator, which, using
liquid's operators (roughly), could be something like:

  PUSHCURRENTINPUTINDEX
  DUP2 INSPECTINPUTVALUE SWAP INSPECTOUTVALUE EQUALVERIFY
  INSPECTOUTPUTSCRIPTPUBKEY "x" EQUAL

which is just ~8 bytes overhead, or could perhaps be something fancier
that supports the batching/consolidation abilities discussed above.

It also needs some way of constructing "unvault[X]", which could be a
TLUV-like construction.

That all seems possible to me; though certainly needs more work/thought
than just having dedicated opcodes and stuffing the data directly in
the sPK.

Cheers,
aj


 permalink raw reply	[|nested]
2023-01-09 20:32     ` James O'Beirne

  
From: James O'Beirne @ 2023-01-10 13:35 UTC (permalink / raw)
  To: Greg Sanders;  Bitcoin Protocol Discussion

[-- Attachment #1: Type: text/plain, Size: 2567 bytes --]

Greg explained his suggestion to me off-list, and I think it's a good one.
To summarize, consider the normal "output flow" of an expected vault use:

(i) output to be vaulted
  => (ii) OP_VAULT output
    => (iii) OP_UNVAULT "trigger" output
      => (iv) final output

In my existing draft implementation, all outputs aside from (iii), the
OP_UNVAULT trigger, can be P2TR or P2WSH. In other words, those outputs can
hide their true script until spend. In my draft, the OP_UNVAULT trigger had
to be bare so that the script interpreter could inspect part of it for
validity: "does this OP_UNVAULT have the same <recovery-spk-hash> and
<spend-delay> as the OP_VAULT?"

If that output wasn't bare, because the <target-hash> is variable at the
time of OP_UNVAULT output creation, the script interpreter would have no
way of constructing the expected scriptPubKey.

Greg's suggestion would allow that output to be any kind of script. He
suggests to put the <target-hash> onto the witness stack when spending the
OP_VAULT output (and creating the OP_UNVAULT output). If we did that, the
script interpreter could e.g. use a NUMS point (i.e. a publicly known point
with no usable private key) to construct a Taproot configuration that looks
like

  tr(NUMS, {<OP_UNVAULT <recovery-key> <spend-delay> <target-hash>})

and check if the scriptPubKey of the proposed OP_UNVAULT output matches
that. This would allow all outputs in vault lifecycles to be P2TR, for
example, which would conceal the operation of the vault - a very nice
feature!

This would also allow the OP_VAULT/OP_UNVAULT opcodes to be implemented as
Taproot-only OP_SUCCESSx opcodes, if that was decided to be preferable.

The problem is how to (and whether to) enable something similar for witness
v0 outputs. For example, if we want the (ii) and (iii) output scripts to
live behind P2WSH. One (kind of hacky) option to enable this is to have the
script interpreter construct the expected OP_UNVAULT scriptPubKey on the
basis of what witness version it sees. For example, if it sees "OP_0 <32
bytes data>", it would use <target-hash> on the witness stack to construct
a fitting P2WSH scriptPubKey that is compatible with the OP_VAULT being
spent, and then match against that. But if it detects "OP_1 <32 bytes
data>", it would do the same process for an expected Taproot-with-NUMS
output.

---

Anyway, sorry if that was more verbose than necessary, but I think it's a
really great suggestion from Greg. I'll look at modifying the
implementation accordingly. I'd be curious to hear what others think as
well.

[-- Attachment #2: Type: text/html, Size: 3037 bytes --]

 permalink raw reply	[|nested]
2023-01-09 16:07 James O'Beirne
  2023-01-09 19:02 ` rot13maxi
  2023-01-10 12:29 ` Anthony Towns

  
From: James O'Beirne @ 2023-01-10 14:17 UTC (permalink / raw)
  To: Bitcoin Protocol Discussion

[-- Attachment #1: Type: text/plain, Size: 3358 bytes --]

Forwarding in some conceptual feedback from the pull request.

From ariard:

> I've few open questions, like if the recovery path should be committed
with a signature rather than protected by a simple scriptpubkey preimage.

That's something I've wondered about too. I have to ruminate on AJ's good
post about this, but a pretty straightforward way of enabling this (at the
expense of some complexity) is to do something like "if
<recovery-path-hash> is 32 bytes, treat it as it's currently used. If it's
33 bytes, use the first byte as a parameter for how to interpret it." To
start with, an extra prefix byte of 0x00 could mean "require a witness
satisfying the scriptPubKey that hashes to the remaining 32 bytes" in the
same way we do the unvault signing. This would enable a "sign-to-recover"
flow at the option of the user, specified during vault creation.

> The current OP_VAULT implementation is using OP_NOP repurposing but this
doesn't seem compatible with Taproot-only extensions (e.g ANYPREVOUT) and
maybe a OP_SUCCESS could be used.

Yes, with Greg's suggestion of putting <target-hash> on the witness stack
during OP_VAULT (-> OP_UNVAULT) spend, we could conceivably move
OP_VAULT/OP_UNVAULT into Taproot-only OP_SUCCESSx opcodes. I haven't
thought hard about how worthwhile it is to preserve the ability to use
OP_VAULT in pre-Taproot contexts.

> There is a conceptual wonder, if a CTV and template malleability approach
wouldn't better suit the vault use-case and allow other ones, as such
better re-usability of primitives.

I dedicated a whole section of the paper ("Precomputed vaults with
covenants") to explaining why precomputed covenant mechanisms have big
shortcomings for vaults.

That said, a number of people have commented about OP_VAULT's ability to
(inefficiently) emulate CTV. I'm still very supportive of CTV, I just don't
really have any uses I personally understand inside and out aside from
vaults... so if others do, they should really post about it on this list
and we should resume working on an activation for CTV!

---

From naumenkogs:

> I'm personally not sure batching withdrawals is that compelling... It's a
nice-to-have, but I'd think about the benefits dropping this feature would
provide.

Having familiarity with a few large-scale custodial operations, I think
batching is a really big deal. And if you're going to support multiple
deposits to the same vault, no support for batching is going to result in a
lot of unnecessary output creation even as a small user if you're, e.g.,
doing weekly automated deposits from an exchange to a vault you've
configured.

Darosior comments:

> On the contrary i think the batching feature is very compelling. The
impossibility to batch Unvaults in Revault is a major drawback: it
significantly increases the cost of any realistic operation (you need one
whole additional transaction per input, and each have likely more than one
output). It also potentially increases the cost on the network (you'd
likely want some sort of anchor output on each Unvault tx, that you might
not spend, so that's 2*n outputs created with n the number of coins spent):
we definitely don't want to prevent batching. The ability to batch the
recovery transactions (what we called Emergency tx in Revault) is also very
compelling but i think your comment was only about batched withdrawals.

[-- Attachment #2: Type: text/html, Size: 14209 bytes --]

 permalink raw reply	[|nested]
2023-01-10 12:29 ` Anthony Towns

  2023-01-11  6:52     ` Anthony Towns
  
From: James O'Beirne @ 2023-01-10 20:22 UTC (permalink / raw)
  To: Anthony Towns;  Bitcoin Protocol Discussion

[-- Attachment #1: Type: text/plain, Size: 4497 bytes --]

Thanks for the thoughtful reply AJ.


> I don't think that makes sense? With a general scheme, you'd only be
> bloating the witness data (perhaps including the witness script) not
> the scriptPubKey?

Sorry, sloppy language on my part. To be charitable, I'm talking about
the "figurative sPK," which of course these days lives in the witness
for script-path-ish spends. Maybe the witness discount means that
"complicated" scripts aren't as big a deal, depending on the actual
difference in raw script size.


> I think it might be better to use a pay-to-contract construction for
> the recovery path, rather than an empty witness.

So I guess the one advantage that what you're proposing has over just
using a recovery-path key signature is that it's all derivable from your
cold privkey; you don't have to worry about accidentally losing the
recovery-path key.

Of course you're still vulnerable to spurious sweeps if the
sha256(secret) value gets found out, which presumably you'd want in an
accessible cache to avoid touching the cold secret every time you want
to sweep.

What do you think about the idea of making the recovery-path
authorization behavior variable on a single byte flag preceding the 32
byte data push, as I mentioned in another post? I think it may make
sense to leave this option open to end-users (and also allow for some
upgradeability).


> I think a generic OP_UNVAULT can be used to simulate OP_CTV: replace
> "<h> OP_CTV" with "<000..0> 0 <h> OP_UNVAULT".

Yup, that's an inefficient way of emulating CTV. If people want CTV, we
should just look at activating CTV. Greg Sanders has a thing about
"jetting" CTV into this proposal (I think) so that the code-paths are
shared, but I haven't figured out how that would work. They really
don't share that much code AFAICT.


> The paper seems to put "OP_UNVAULT" first, but the code seems to
> expect it to come last, not sure what's up with that inconsistency.

Again some sloppy notation on my part. What I sort of meant in the paper
was a kind of functional notation `OP_VAULT(param1, param2, ...)`. Let's
chalk that up to my inexperience actually working on script stuff.


> I think there's maybe a cleverer way of batching / generalising
> checking that input/output amounts match.
>
> [...]
>
>  * set C = the sum of each output that has a vault tag with
>    #recovery=X

This would also need to take into account that the <spend-delay>s are
compatible, but your point is well taken.


> I think one meaningful difference between these two approaches is that
> the current proposal means unvaulting locks up the entire utxo for the
> delay period, rather than just the amount you're trying to unvault.

This is a really good point and I think is one that's important to
incorporate with a change to the existing proposal.

A simple fix for facilitating the use of a "partial revault" while the
OP_UNVAULT UTXO is outstanding would be to allow for an optional
third output that is a redeposit back to the identical OP_VAULT sPK that
is being spent by the OP_UNVAULT transaction, then the script
interpreter would just ensure that the nValue of those two outputs sums
to the sum of the input nValues.

I can see what you're saying about having more generic "group amounts by
compatible vault params, and then compare to similarly grouped outputs,"
but I'm just wondering if there are other uses that enables besides the
partial-revault thing I mentioned above. If not, I'd probably rather just
stick
with something simple like having the third optional re-vault output.


> Changing the unvault construction to have an optional OP_VAULT output
> would remedy that, I think.

Oh - okay, this is what you're saying. Right!

Is that a sufficient change, or are there other benefits that the more
complicated clever-I/O-vault-grouping would enable that you have in
mind?


> What would it look like to just hide all this under taproot?
>
> [...]
>
> It also needs some way of constructing "unvault[X]", which could be a
> TLUV-like construction.
>
> That all seems possible to me; though certainly needs more work/thought
> than just having dedicated opcodes and stuffing the data directly in
> the sPK.

I think this is a very important comparison to do, but I'm eager to see
code for things like this. There have been a lot of handwavey proposals
lately without tangible code artifacts. I'm eager to see what these
alternatives look like in practice - i.e. in functional tests.


Thanks again for the great mail.
James

[-- Attachment #2: Type: text/html, Size: 5913 bytes --]

 permalink raw reply	[|nested]
2023-01-10 20:22   ` James O'Beirne

  
From: Anthony Towns @ 2023-01-11  6:52 UTC (permalink / raw)
  To: James O'Beirne;  Bitcoin Protocol Discussion

On Tue, Jan 10, 2023 at 03:22:54PM -0500, James O'Beirne wrote:
> > I don't think that makes sense? With a general scheme, you'd only be
> > bloating the witness data (perhaps including the witness script) not
> > the scriptPubKey?
> Sorry, sloppy language on my part. To be charitable, I'm talking about
> the "figurative sPK," which of course these days lives in the witness
> for script-path-ish spends. Maybe the witness discount means that
> "complicated" scripts aren't as big a deal, depending on the actual
> difference in raw script size.

Sure. I think there's three aspects that matter for the witness script:

 1) it shouldn't be long/costly to do things that are common and easy:
    if you can express "OP_VAULT" in ~70 bytes, you shouldn't have to
    spend 1000 bytes to do so; if it can be cheap to validate, you
    shouldn't have to pay 100x markup in fees to use it. With the
    exception of things that build up from basics (like
    CAT/CHECKSIGFROMSTACK approaches), I think this is mostly fine
    though.

 2) once someone figures out a design, it should be easy to reuse;
    but I think that's not a big deal: you just write up a spec for
    your script, and people use that in their different wallet software,
    much like the specialised scripts for lightning HTLCs

 3) primitives should be designed to be easy to safely build on and
    scripts should be as easy as possible to analyse once written;
    ie, we want things more like miniscript than "The story of Mel,
    a Real Programmer"

With some caveats (like that using the cold wallet xpub to scan the
blockchain before you've frozen all your funds is dangerous), OP_VAULT
seems really good on all those fronts, of course.

> > I think it might be better to use a pay-to-contract construction for
> > the recovery path, rather than an empty witness.
> So I guess the one advantage that what you're proposing has over just
> using a recovery-path key signature is that it's all derivable from your
> cold privkey; you don't have to worry about accidentally losing the
> recovery-path key.
> Of course you're still vulnerable to spurious sweeps if the
> sha256(secret) value gets found out, which presumably you'd want in an
> accessible cache to avoid touching the cold secret every time you want
> to sweep.

Sure, "sha256(secret)" itself needs to be semi-secret -- it allows anyone
who knows it to freeze your funds, even if it doesn't allow anyone to
steal them. You could presumably do all the usual things to protect that
secret: split it up with secret sharing; put it in a hardware wallet;
keep it offline; etc.

> What do you think about the idea of making the recovery-path
> authorization behavior variable on a single byte flag preceding the 32
> byte data push, as I mentioned in another post?

] "if
] <recovery-path-hash> is 32 bytes, treat it as it's currently used. If it's
] 33 bytes, use the first byte as a parameter for how to interpret it." To
] start with, an extra prefix byte of 0x00 could mean "require a witness
] satisfying the scriptPubKey that hashes to the remaining 32 bytes" in the
] same way we do the unvault signing.

I don't think 33 bytes would be enough? There isn't really a way to
commit to the recovery destination within the script? So I think you'd
need "<32 byte recovery-path-hash><n byte scriptPubKey>"

Aside from that, my opinion's one/all of:

a) sounds fine

b) maybe you could just always have it include a scriptPubKey? for the
times when you just want "reveal the cold wallet preimage" just have
the scriptPubKey be the single byte "OP_TRUE"; for the times when you
it to be "reveal random preimage" you'd have it be the 22 byte "HASH160
<hash160(sha256(secret))> EQUAL"?

c) delegation to a script is a great idea, that's come up multiple times
(OP_EVAL, BIP117, graftroot) -- it's probably better to have it
available as a generic feature, than bolted on to particular features

> > I think a generic OP_UNVAULT can be used to simulate OP_CTV: replace
> > "<h> OP_CTV" with "<000..0> 0 <h> OP_UNVAULT".
> Yup, that's an inefficient way of emulating CTV.

Sure; I think it's only interesting in establishing how powerful the
construct is in the abstract. It's not an exact match for CTV since it
hashes some things differently.

I don't really think it's necessarily that inefficient fwiw; "0 SHA256
0 <h> UNVAULT" is only 3 more bytes than "<h> CTV", could give you an
unspendable recovery path, provided UNVAULT wants either a BIP341 tagged
hash (which is what the implementation does, by the looks), or a HASH256
for the recovery path.

(Again, this assumes UNVAULT is available in script, and isn't just a
special scriptPubKey type)

> > I think there's maybe a cleverer way of batching / generalising
> > checking that input/output amounts match.
> > [...]
> >  * set C = the sum of each output that has a vault tag with
> >    #recovery=X
> This would also need to take into account that the <spend-delay>s are
> compatible, but your point is well taken.

Sure, I guess output/UNVAULT delay >= input/VAULT delay would be
sufficient for that.

I guess having all that stuff exposed in the scriptPubKey would be
slightly annoying for griefing -- you set your wallet delay to be 1008
blocks, and someone sends to your vault with a 1007 block delay, or a
6 block delay: does your wallet notice the tx? if it does, what do you
do with those funds?

Maybe that would be a(nother) good reason to hide the OP_VAULT side of
things in taproot (or p2wsh): then you just have a (shorter) taproot
sPK that encodes both things, and people normally don't even have enough
information to correctly tweak your sPK to have a different delay.

> I can see what you're saying about having more generic "group amounts by
> compatible vault params, and then compare to similarly grouped outputs,"

I mostly wrote that up because that's what I imagined your batching
doing before I'd finished reading...

A big disadvantage of that approach compared to yours is that you have
to analyse a potentially arbitrarily large transaction as a whole while
validating each input -- this input uses recovery key XXX which output's
match; this input uses recovery key YYY; oh, this one uses XXX again;
etc. With your approach, you only have to look at particular outputs.

An advantage of that extra complexity is that you could combine operations
from multiple different vaults into a single transaction, potentially
along with fees or a coinjoin or whatever else. Maybe that would be
interesting if this were something you could code in script via some
generic opcodes; but here it would be extra complexity in consensus code,
and it doesn't seem like a good match for OP_VAULT's design parameters.

Cheers,
aj


 permalink raw reply	[|nested]
2023-01-17  7:46 ` Anthony Towns
  2023-01-20 17:43 ` James O'Beirne
  
From: Andrew Chow @ 2023-01-16 23:47 UTC (permalink / raw)
  To: bitcoin-dev

Hi James,

This seems like a promising proposal, but I noticed have a few issues
regarding batching and privacy.

It seems like this proposal will encourage address reuse for vaults, at
least in some parts. It seems like it would not be difficult to ensure
that each vault address was unique through the use of key derivation.
The recovery and unvault scripts could be produced from ranged
descriptors and so there would each vault address would be unique as
each recovery and unvault script is unique. It would not be hard to have
descriptors for vaults, which would then allow for usage of other
descriptors and miniscript into the recovery and unvault scripts.

However the current construction makes it impossible to spend these
vaults together. Since OP_VAULT requires the recovery script of the
unvault output to match what's provided in the input, if there are
multiple inputs with different recovery scripts, then the transaction
will fail. I'm not sure how this could be solved though.

But from my reading of the code, it looks like the unvault scripts can
be unique, so at least address reuse can be avoided here. It just means
that the recovery scripts must be the same, and this would leave an
identifying mark on chain for every unvault. An observer would be able
to correlate unvault transactions by the hashes of the recovery scripts,
and I think this would be rather detrimental to user privacy, not to
mention that sweeping to recovery would also reveal all of your coins too.

On the topic of address reuse, the implemented optional re-vault output
explicitly requires address reuse, as well as breaking the batched
unvaulting of scripts that have different unvault scripts. It's
currently implemented as requiring the unvault script to exactly match
the prevout script of the inputs being spent. This means that all inputs
must have the same script. I think it would be sufficient to do the same
check as the OP_UNVAULT script and just require that the recovery script
and the delay are the same, with the hash of the trigger script being
provided in the input in the same way the target hash is provided for
OP_UNVAULT. This would break the address reuse requirement.

I'm also not convinced that OP_VAULT and OP_UNVAULT should be allowed
for bare and P2WSH outputs. It seems like it would make sense to just
limit their usage to tapscripts as this would simply their implementation.


Andrew

On 01/09/2023 11:07 AM, James O'Beirne via bitcoin-dev wrote:
> For the last few years, I've been interested in vaults as a way to
> substantially derisk custodying Bitcoin, both at personal and commercial
> scales. Instead of abating with familiarity, as enthusiasm sometimes
> does, my conviction that vaults are an almost necessary part of bitcoin's
> viability has only grown over the years.
>
> Since people first started discussing vaults, it's been pretty clear that
> some kind of covenant-enabling consensus functionality is necessary to
> provide the feature set necessary to make vault use practical.
>
> Earlier last year I experimented with using OP_CTV[1], a limited covenant
> mechanism, to implement a "minimum-viable" vault design. I found that the
> inherent limitations of a precomputed covenant scheme left the resulting
> vault implementation wanting, even though it was an improvement over
> existing strategies that rely on presigned transactions and (hopefully)
> ephemeral keys.
>
> But I also found proposed "general" covenant schemes to be
> unsuitable for this use. The bloated scriptPubKeys, both in size and
> complexity, that would result when implementing something like a vault
> weren't encouraging. Also importantly, the social-consensus quagmire
> regarding which covenant proposal to actually deploy feels at times
> intractable.
>
> As a result, I wanted to explore a middle way: a design solely concerned
> with making the best vault use possible, with covenant functionality as a
> secondary consideration. In other words, a proposal that would deliver
> the safety benefits of vaults to users without getting hung up on
> trying to solve the general problem of covenants.
>
> At first this design, OP_VAULT, was just sort of a pipe dream. But as I
> did more thinking (and eventually implementing) I became more convinced
> that, even if it isn't considered for soft-fork, it is a worthwhile
> device to serve as a standard benchmark against which other proposals
> might be judged.
>
> I wrote a paper that summarizes my findings and the resulting proposal:
> https://jameso.be/vaults.pdf
>
> along with an accompanying draft implementation:
> https://github.com/bitcoin/bitcoin/pull/26857
>
> I might work on a BIP if there's interest.
>
> James
>
> [1]: https://github.com/jamesob/simple-ctv-vault




 permalink raw reply	[|nested]
2023-01-16 23:47 [bitcoin-dev] OP_VAULT: a new vault proposal Andrew Chow

  2023-01-18 19:00   ` Billy Tetrud
  2023-01-18 22:45   ` James O'Beirne
  2023-01-20 17:43 ` James O'Beirne
  
From: Anthony Towns @ 2023-01-17  7:46 UTC (permalink / raw)
  To: Andrew Chow, Bitcoin Protocol Discussion

On Mon, Jan 16, 2023 at 11:47:09PM +0000, Andrew Chow via bitcoin-dev wrote:
> It seems like this proposal will encourage address reuse for vaults,

(That is listed as an explicit goal: "A single vault scriptPubKey should
be able to "receive" multiple deposits")

> However the current construction makes it impossible to spend these
> vaults together. Since OP_VAULT requires the recovery script of the
> unvault output to match what's provided in the input,

I don't think this part is a big problem -- the recovery path requires
revealing a secret, but if you separate that secret from the recovery
path sPK, you could vary the secret. ie:

  unvault1 delay recovery1 VAULT
  unvault2 delay recovery2 VAULT

where recovery1 = SHA256(SHA256(secret1), rSPK) and recovery2 =
SHA256(SHA256(secret2), rSPK), and both are spendable when the top stack
element is secretN and the first output pays at least the sum of all
the OP_VAULT using inputs to rSPK. So batched recovery could work fine,
I think.

(If you're using the same "recovery" parameter to each VAULT, then
you're revealing which txs are in your vault at spend time, rather than
at receive time, which doesn't seem all that much better to me)

But the problem with this is it prevents you from combining vaults when
spending normally: so if you've got a bunch of vaults with 1 BTC each,
and want to spend 10 BTC on a house, you'll need to make 11 separate
transactions:

  * 10 txs each spending a single vault utxo, satisfying
      <uN> <delay> <rN> OP_VAULT
    via the uN path, creating an output of
      <outhash> <delay> <rN> OP_UNVAULT

  * 1 tx spending all the OP_UNVAULT outputs to a common set of outputs
    <uN>, with nSequence set to a relative timelock of at least <delay>

Whereas if you use an identical OP_VAULT script for all the utxos in
your vault, that can look like:

  * 1 tx, spending all the vault utxos, to a single OP_UNVAULT output,
    with the same <delay> <rN> that all the inputs share.

  * 1 tx spending the OP_UNVAULT output after a delay

But maybe you can get the best of both worlds just by having the unvault
path for OP_VAULT require you to put the vout number for its corresponding
OP_UNVAULT output on the stack? Then if you're doing address reuse, you
use a single vout for multiple inputs; and if you're avoiding address
reuse, you use multiple outputs, and provide the mapping between inputs
and outputs explicitly.

Cheers,
aj



 permalink raw reply	[|nested]
2023-01-17  7:46 ` Anthony Towns

  2023-01-18 23:37     ` James O'Beirne
  2023-01-18 22:45   ` James O'Beirne
  
From: Billy Tetrud @ 2023-01-18 19:00 UTC (permalink / raw)
  To: Anthony Towns, Bitcoin Protocol Discussion

[-- Attachment #1: Type: text/plain, Size: 9348 bytes --]

I like the proposal of a targeted wallet vault opcode. It keeps things
constrained, limiting objections to those of the form "but if we had X it
would do all this and more so why add this complexity when it will be
obsoleted in the future?"

> An idealized vault
> no existing vault design meets this set of features.

My proposal for efficient wallet vaults
<https://github.com/fresheneesz/bip-efficient-bitcoin-vaults> was designed
to meet all of those criteria, and allows batching as well. It also allows
sending in a single transaction vs the two it would take with OP_VAULT and
several other benefits. However, it uses a general covenant opcode to do
it, along with several other new opcodes specified in that write up.

>  it must first be spent into an OP_UNVAULT output

I don't see in the write up how a node verifies that the destination of a
spend using an OP_VAULT output uses an appropriate OP_UNVAULT script. I see
you mentioned above (but not in the write up) that the script pub key needs
to be bare. But it would be very helpful if you detailed exactly how this
is intended to be done in that document.

It seems that Greg Sanders noticed the same thing. I like his suggestion as
you reworded it above, makes a lot of sense.

> I think the fix is just requiring a single additional witness data item
during OP_VAULT spend (for unvault path), mandating the
<target-outputs-hash> to be included in the witness stack as an input to
OP_VAULT opcode, and transaction introspection then checks to make sure the
witness item and the corresponding output script template matches the
expected.

>  If it becomes necessary to make use of the recovery path, the recovery
scriptPubKey will be revealed, which means that any other vaults with that
recovery path may be swept there by an unauthenticated party.

Another issue is that the recovery path becomes the easiest mechanism of
attack. It would usually be prudent to store this recovery address with
every key to the vault, and potentially in other places as well, so as to
minimize the possibility that the funds are lost or stolen. In such a
situation, this means that an attacker that finds any key can grief the
vault by spending it to its recovery address. My "efficient wallet vault"
design I mentioned above doesn't have this issue, nor the associated
batching DoS attack.

> if the recovery path should be committed with a signature
> This would enable a "sign-to-recover" flow at the option of the user,
specified during vault creation.

This is rather limiting isn't it? Losing the key required to sign loses
your recovery option. Seems brittle. It seems better to me to include a
<recovery-initiation-spk-hash> that operates similarly to
<recovery-spk-hash> - where some arbitrary script must be fulfilled to
allow the recovery path to be spent. For a recovery path, you'd probably
often want one of the keys required to spend from the recovery address,
since without access to one of those keys, you can't spend from the
recovery address anyway (and the spend path is an effective burn). Having
some ability to allow an n-of-m situation for triggering the recovery path
seems desirable.

>  What do you think about the idea of making the recovery-path
authorization behavior variable on a single byte flag preceding the 32 byte
data push, as I mentioned in another post?

A more arbitrary construct here that allows you to use any kind of recovery
script would be much more flexible and preclude the need for any kind of
switching like this. It seems like it would also solve the issue Andrew
Chow mentioned where recovery transactions can only be batched if they all
share the same recovery output, since each output can simply include the
appropriate witness matching its recovery scriptPubkey.

Tho I don't think I quite understand why you mention the constraint
requiring recovery batching to only be done with vault outputs that have
matching recovery destinations. Wouldn't it be reasonably possible to allow
recovery outputs with any recovery address to be batched, and the amount
sums sent to each to be added up and verified?

>  1. script validation rules could require some allowable “range” of
amount discounts
>  seems like a bad design

> 2. script validation rules could require that the unvault/recovery
outputs preserve the full value
> seems like the preferable approach

Both have tradeoffs. I would not call #1 an inherently bad design. I would
point out that for 2, disallowing the spending of vault funds without
access to already-unvaulted bitcoin seems like a very inconvenient design,
since it would require you in the best of cases to create more complex
transactions (or cpfp transaction chains) involving a 2nd wallet that you
involve in the unvaulting process, and in the worst case (if you have no
other bitcoin or other money on hand) you have to go asking a 3rd party for
their bitcoin to use in the unvaulting process. If someday wallet vaults
are the standard wallet construct, people might not even want to have a
non-vault wallet just for use in unvaulting.

#1 is the approach I used to design OP_LIMITFEECONTRIBUTION
<https://github.com/fresheneesz/bip-efficient-bitcoin-vaults/blob/main/lfc/bip-limit-fee-contribution.md>,
which allows for a fee-range specification that depends on recent median
fees included in blocks. This allows rather flexibly limiting fees to a
particular range of "priorities" regardless of fee environment.

For #2, it seems like Jeremy Rubin's Sponsor transactions would be ideal
for facilitation of adding a fee to an unvaulting transaction.

>  In the case of a withdrawal, unvaulted funds can skip the “warm” wallet
step that precomputed vault funds must pass through on their way to
destinations only known at unvault time.

Hmm, it seems inaccurate to say that step is "skipped". While there isn't a
warm wallet step, its replaced with an OP_UNVAULT script step. So its not
skipped as much as modified I think, right?

It looks like the way the OP_UNVAULT is specified prevents any use where
you don't know the full set of outputs, which might happen in cases where
certain sighash flags might be useful (signing some outputs you know, but
allowing outputs that you don't know to be added later). This is another
thing my "efficient wallet vault" design should allow.





On Tue, Jan 17, 2023 at 1:47 AM Anthony Towns via bitcoin-dev <
bitcoin-dev@lists•linuxfoundation.org> wrote:

> On Mon, Jan 16, 2023 at 11:47:09PM +0000, Andrew Chow via bitcoin-dev
> wrote:
> > It seems like this proposal will encourage address reuse for vaults,
>
> (That is listed as an explicit goal: "A single vault scriptPubKey should
> be able to "receive" multiple deposits")
>
> > However the current construction makes it impossible to spend these
> > vaults together. Since OP_VAULT requires the recovery script of the
> > unvault output to match what's provided in the input,
>
> I don't think this part is a big problem -- the recovery path requires
> revealing a secret, but if you separate that secret from the recovery
> path sPK, you could vary the secret. ie:
>
>   unvault1 delay recovery1 VAULT
>   unvault2 delay recovery2 VAULT
>
> where recovery1 = SHA256(SHA256(secret1), rSPK) and recovery2 =
> SHA256(SHA256(secret2), rSPK), and both are spendable when the top stack
> element is secretN and the first output pays at least the sum of all
> the OP_VAULT using inputs to rSPK. So batched recovery could work fine,
> I think.
>
> (If you're using the same "recovery" parameter to each VAULT, then
> you're revealing which txs are in your vault at spend time, rather than
> at receive time, which doesn't seem all that much better to me)
>
> But the problem with this is it prevents you from combining vaults when
> spending normally: so if you've got a bunch of vaults with 1 BTC each,
> and want to spend 10 BTC on a house, you'll need to make 11 separate
> transactions:
>
>   * 10 txs each spending a single vault utxo, satisfying
>       <uN> <delay> <rN> OP_VAULT
>     via the uN path, creating an output of
>       <outhash> <delay> <rN> OP_UNVAULT
>
>   * 1 tx spending all the OP_UNVAULT outputs to a common set of outputs
>     <uN>, with nSequence set to a relative timelock of at least <delay>
>
> Whereas if you use an identical OP_VAULT script for all the utxos in
> your vault, that can look like:
>
>   * 1 tx, spending all the vault utxos, to a single OP_UNVAULT output,
>     with the same <delay> <rN> that all the inputs share.
>
>   * 1 tx spending the OP_UNVAULT output after a delay
>
> But maybe you can get the best of both worlds just by having the unvault
> path for OP_VAULT require you to put the vout number for its corresponding
> OP_UNVAULT output on the stack? Then if you're doing address reuse, you
> use a single vout for multiple inputs; and if you're avoiding address
> reuse, you use multiple outputs, and provide the mapping between inputs
> and outputs explicitly.
>
> Cheers,
> aj
>
> _______________________________________________
> bitcoin-dev mailing list
> bitcoin-dev@lists•linuxfoundation.org
> https://lists.linuxfoundation.org/mailman/listinfo/bitcoin-dev
>

[-- Attachment #2: Type: text/html, Size: 11569 bytes --]

 permalink raw reply	[|nested]
2023-01-17  7:46 ` Anthony Towns
  2023-01-18 19:00   ` Billy Tetrud

  
From: James O'Beirne @ 2023-01-18 22:45 UTC (permalink / raw)
  To: Anthony Towns, Bitcoin Protocol Discussion

[-- Attachment #1: Type: text/plain, Size: 2169 bytes --]

I've implemented three changes based on suggestions from Greg Sanders
and AJ Towns.

I've segmented the changes into commits that should be
reasonable to follow, even though I'll probably rearrange the commit
structure later on.

1. Greg's suggestion: OP_UNVAULT outputs can now live behind
scripthashes. This means that the lifecycle of a vault can live entirely
within, say, Taproot. In this case the only thing that would reveal
the operation of the vault would be the content of the witness stack
when triggering an unvault or recovering. So I think no real privacy
benefits over the previous scheme, but certainly some efficiency ones
since we're moving more content from the scriptPubKey into the witness.

 Commit here:
https://github.com/bitcoin/bitcoin/pull/26857/commits/cd33d120c67cda7eb5c6bbfe3a1ea9fb6c5b93d1

2. AJ's suggestion: unvault trigger transactions can now have an extra
"revault" output that redeposits some balance to the same vault
scriptPubKey from which it came. This is nice because if the delay
period is long, you may want to manage a remaining vault balance
separately while the spent balance is pending an unvault.

 Commit here:
https://github.com/bitcoin/bitcoin/pull/26857/commits/cf3764fb503bc17c4438d1322ecf780b9dc3ef30

3. AJ's suggestion: instead of specifying <recovery-spk-hash>, introduce
a replacement parameter: <recovery-params>. This contains the same
target recovery sPK hash as before, but the remaining bytes contain a
scriptPubKey that functions as authorization for the recovery process.
This allows users to optionally avoid the risk of "recovery replays" at
the expense of having to maintain a recovery key. Users can opt out of
this by passing OP_TRUE for the recovery sPK. I guess maybe I could even
support just omitting an sPK altogether for the legacy behavior.

Commit here:
https://github.com/bitcoin/bitcoin/pull/26857/commits/fdfd5e93f96856fbb41243441177a40ebbac6085


The suggestions were good ones, and I think they've improved the
proposal.

My next steps are to do minor updates to the paper and start writing a
BIP draft.

Thanks to achow for the valuable feedback, which I'm still mulling on.

James

[-- Attachment #2: Type: text/html, Size: 2800 bytes --]

 permalink raw reply	[|nested]
2023-01-18 19:00   ` Billy Tetrud

  2023-01-19 22:49       ` Billy Tetrud
  
From: James O'Beirne @ 2023-01-18 23:37 UTC (permalink / raw)
  To: Billy Tetrud, Bitcoin Protocol Discussion

[-- Attachment #1: Type: text/plain, Size: 3793 bytes --]

> I don't see in the write up how a node verifies that the destination
> of a spend using an OP_VAULT output uses an appropriate OP_UNVAULT
> script.

It's probably quicker for you to just read through the
implementation that I reference in the last section of the paper.

https://github.com/bitcoin/bitcoin/blob/fdfd5e93f96856fbb41243441177a40ebbac6085/src/script/interpreter.cpp#L1419-L1456

> It would usually be prudent to store this recovery address with every
> key to the vault, ...

I'm not sure I really follow here. Worth noting now that in OP_VAULT the
recovery path can be optionally gated by an arbitrary scriptPubKey.

> This is rather limiting isn't it? Losing the key required to sign
> loses your recovery option.

This functionality is optional in OP_VAULT as of today. You can specify
OP_TRUE (or maybe I should allow empty?) in the <recovery-params> to
disable any signing necessary for recovery.

> Wouldn't it be reasonably possible to allow recovery outputs with any
> recovery address to be batched, and the amount sums sent to each to be
> added up and verified?

I think the space savings from this is pretty negligible, since you're
just saving on the transaction overhead, and it makes the implementation
decently more complicated. One benefit might be sharing a common
fee-management output (e.g. ephemeral anchor) across the separate vaults
being recovered.

> If someday wallet vaults are the standard wallet construct, people
> might not even want to have a non-vault wallet just for use in
> unvaulting.

If you truly lacked any non-vaulted UTXOs and couldn't get any at a
reasonable price (?), I can imagine there might be a mechanism where you
include a payout output to some third party in a drafted unvault trigger
transaction, and they provide a spend of the ephemeral output.

Though you do raise a good point that this construction as written may
not be compatible with SIGHASH_GROUP... I'd have to think about that
one.

> Hmm, it seems inaccurate to say that step is "skipped". While there
> isn't a warm wallet step, its replaced with an OP_UNVAULT script step.

It is "skipped" in the sense that your bitcoin can't be stolen by having
to pass through some intermediate wallet during an authorized withdrawal
to a given target, in the way that they could if you had to prespecify
an unvault target when creating the vault.


---


> My proposal for efficient wallet vaults was designed to meet all of
> those criteria, and allows batching as well.

Probably a discussion of your proposal merits a different thread, but
some thoughts that occur:


> [from the README]
>
> OP_BEFOREBLOCKVERIFY - Verifies that the block the transaction is
> within has a block height below a particular number. This allows a
> spend-path to expire.

I think this breaks fundamental reorgability of transactions. I think
some of the other opcodes, e.g the one that limits fee contribution on
the basis of historical feerate, are going to be similarly
controversial.

> This is done by using a static intermediate address that has no values
> that are unique to the particular wallet vault address.

Does mean either that (i) this proposal doesn't have dynamic unvaulting
targets or, (ii) if you do, in order to be batch unvaulted, vaulted
coins need to first be spent into this intermediate output?

It sounds like (ii) is the case, given that your unvault target
specification lives in (I think?) the witness for the spend creating the
intermediate output.

If the intermediate address doesn't have any values which are unique to
a particular vault, how do you authorize recoveries from it?

---

Personally I think if you'd like to pursue your proposal, it'd be
valuable to see a full implementation. Might also make it easier to
assess the viability of the proposal.

[-- Attachment #2: Type: text/html, Size: 4409 bytes --]

 permalink raw reply	[|nested]
2023-01-18 23:37     ` James O'Beirne

  
From: Billy Tetrud @ 2023-01-19 22:49 UTC (permalink / raw)
  To: James O'Beirne;  Bitcoin Protocol Discussion

[-- Attachment #1: Type: text/plain, Size: 10032 bytes --]

>> It would usually be prudent to store this recovery address with every key
to the vault, ...
> Worth noting now that in OP_VAULT the recovery path can be optionally
gated by an arbitrary scriptPubKey.

Gating by a scriptPubKey solves the problem I was talking about there.
However, thinking about it more, I realized doing this basically turns
OP_VAULT into something able to do general covenants. By making
`unvault-target-hash`
unsatisfiable (set to some random number that isn't derived from a hash)
the delay wouldn't matter, but arbitrary conditions can be set on spending
the utxo to the "recovery address" which could be another OP_UNVAULT
destination. It seems like that could be used as a general CTV-like
covenant.

>> Wouldn't it be reasonably possible to allow recovery outputs with any
>> recovery address to be batched, and the amount sums sent to each to be
>> added up and verified?
> I think the space savings from this is pretty negligible

Besides space savings, there's the consideration of the usability of the
vault and downstream code complexity. One of the criteria I designed the
"efficient wallet vaults" opcodes for is that the vault spend should be
spendable in situations where a normal output is spendable as much as
possible. Having a constraint that prevents one type of otherwise spendable
output from being combined with another type would add complexity to all
downstream code which now has to have special cases - either a simple error
and recovery if they just want to disallow that type of opcode being
combined with other types (which may degrade the user experience by asking
them to provide a different utxo or do an unvaulting first), or some kind
of special case handling to make it work. Any kind of hybrid wallet
incorporating a vault (eg a wallet that combines a vault and a hot address
or lightning channel, kind of like Phoenix combines a lightning channel and
a normal onchain wallet) would need to deal with this kind of extra
complexity during utxo selection.

Are there currently any situations where one otherwise-spendable utxo can't
be mixed with another? If not, this added edge case deserves some extra
consideration I think.

> I can imagine there might be a mechanism where you include a payout
output to some third party in a drafted unvault trigger transaction, and
they provide a spend of the ephemeral output.

I agree that's doable. I just think it merits some consideration as to
whether that complexity (both for downstream code and for users) is a
favorable trade off vs having a solution to reasonably bound fees spendable
from the vault.

Consider the case where a self-custodying user would have a small set of
keys (2? 3?) and use all those keys to secure their vault, and just 1 of
them to secure their hot wallet. It doesn't seem an implausible case and I
could imagine that kind of set up becoming quite common. In such a case, if
the hot wallet key is stolen, it means one vault key is also stolen and the
hot wallets funds could be stolen at the same time as an unvaulting is
triggered. The need to figure out how to coordinate a 3rd party's help to
recover is at best an added difficulty and delay.

An alternative would be to keep a completely separate hot wallet key that
isn't use as part of the vault. But because key storage is by far the most
difficult and costly part of self-custody, every additional key that needs
to be stored is a significant additional burden (that's one of the benefits
of wallet vaults - fewer seeds needed for a given amount of
security/redundancy).

Another alternative would be to have a hot wallet that for its primary
spend-path uses a memory-only passphrase on one of the vault seeds (so
compromise of a vault seed won't compromise the hot wallet) and has a
recovery spend path that uses multiple (or all) vault seeds to recover if
you forget the passphrase. It certainly seems like something can be worked
out here to make the end user experience reasonable, but the additional
operational complexity this would entail still deserves consideration.

>> OP_BEFOREBLOCKVERIFY
> I think this breaks fundamental reorgability of transactions.

I discuss this in the Reorg Safety section here
<https://github.com/fresheneesz/bip-efficient-bitcoin-vaults/blob/main/bbv/bip-beforeblockverify.md#reorg-safety>
.

>> This is done by using a static intermediate address that has no values
>> that are unique to the particular wallet vault address.
> Does mean .. that (ii) .. in order to be batch unvaulted [with dynamic
unvaulting
targets], vaulted
> coins need to first be spent into this intermediate output?

It does support dynamic unvaulting using OP_PUSHOUTPUTSTACK
<https://github.com/fresheneesz/bip-efficient-bitcoin-vaults/blob/main/pos/bip-pushoutputstack.md>,
which adds data to an output that carries over to the execution when
spending the output created by a utxo that uses OP_PUSHOUTPUTSTACK. So the
design is done such that once the intermediate output has been confirmed
and the unvaulting delay has passed, it is then fully owned by the
recipient without a second transaction (because of the use of
OP_BEFOREBLOCKVERIFY). If OP_BEFOREBLOCKVERIFY is deemed to be
unacceptable, then the intermediate output is fully intermediate and a 2nd
transaction would be required to get it to its committed recipient.

> it'd be valuable to see a full implementation

While OP_BEFOREBLOCKVERIFY can be dropped with only somewhat minor degraded
usability, OP_PUSHOUTPUTSTACK is necessary for the proposal to work as
intended. I would want to see some support for the high-level concepts it
introduces before spending significant time on an implementation. It does
something fundamentally new that other opcodes haven't done: add "hidden"
data onto the output that allows for committing to destination addresses.
Maybe something along the line of Greg Sanders' suggestion for your
proposal could replace the need for this, but I'm not sure its possible
with how OP_CD is designed.

On Wed, Jan 18, 2023 at 5:38 PM James O'Beirne <james.obeirne@gmail•com>
wrote:

> > I don't see in the write up how a node verifies that the destination
> > of a spend using an OP_VAULT output uses an appropriate OP_UNVAULT
> > script.
>
> It's probably quicker for you to just read through the
> implementation that I reference in the last section of the paper.
>
>
> https://github.com/bitcoin/bitcoin/blob/fdfd5e93f96856fbb41243441177a40ebbac6085/src/script/interpreter.cpp#L1419-L1456
>
> > It would usually be prudent to store this recovery address with every
> > key to the vault, ...
>
> I'm not sure I really follow here. Worth noting now that in OP_VAULT the
> recovery path can be optionally gated by an arbitrary scriptPubKey.
>
> > This is rather limiting isn't it? Losing the key required to sign
> > loses your recovery option.
>
> This functionality is optional in OP_VAULT as of today. You can specify
> OP_TRUE (or maybe I should allow empty?) in the <recovery-params> to
> disable any signing necessary for recovery.
>
> > Wouldn't it be reasonably possible to allow recovery outputs with any
> > recovery address to be batched, and the amount sums sent to each to be
> > added up and verified?
>
> I think the space savings from this is pretty negligible, since you're
> just saving on the transaction overhead, and it makes the implementation
> decently more complicated. One benefit might be sharing a common
> fee-management output (e.g. ephemeral anchor) across the separate vaults
> being recovered.
>
> > If someday wallet vaults are the standard wallet construct, people
> > might not even want to have a non-vault wallet just for use in
> > unvaulting.
>
> If you truly lacked any non-vaulted UTXOs and couldn't get any at a
> reasonable price (?), I can imagine there might be a mechanism where you
> include a payout output to some third party in a drafted unvault trigger
> transaction, and they provide a spend of the ephemeral output.
>
> Though you do raise a good point that this construction as written may
> not be compatible with SIGHASH_GROUP... I'd have to think about that
> one.
>
> > Hmm, it seems inaccurate to say that step is "skipped". While there
> > isn't a warm wallet step, its replaced with an OP_UNVAULT script step.
>
> It is "skipped" in the sense that your bitcoin can't be stolen by having
> to pass through some intermediate wallet during an authorized withdrawal
> to a given target, in the way that they could if you had to prespecify
> an unvault target when creating the vault.
>
>
> ---
>
>
> > My proposal for efficient wallet vaults was designed to meet all of
> > those criteria, and allows batching as well.
>
> Probably a discussion of your proposal merits a different thread, but
> some thoughts that occur:
>
>
> > [from the README]
> >
> > OP_BEFOREBLOCKVERIFY - Verifies that the block the transaction is
> > within has a block height below a particular number. This allows a
> > spend-path to expire.
>
> I think this breaks fundamental reorgability of transactions. I think
> some of the other opcodes, e.g the one that limits fee contribution on
> the basis of historical feerate, are going to be similarly
> controversial.
>
> > This is done by using a static intermediate address that has no values
> > that are unique to the particular wallet vault address.
>
> Does mean either that (i) this proposal doesn't have dynamic unvaulting
> targets or, (ii) if you do, in order to be batch unvaulted, vaulted
> coins need to first be spent into this intermediate output?
>
> It sounds like (ii) is the case, given that your unvault target
> specification lives in (I think?) the witness for the spend creating the
> intermediate output.
>
> If the intermediate address doesn't have any values which are unique to
> a particular vault, how do you authorize recoveries from it?
>
> ---
>
> Personally I think if you'd like to pursue your proposal, it'd be
> valuable to see a full implementation. Might also make it easier to
> assess the viability of the proposal.
>

[-- Attachment #2: Type: text/html, Size: 11567 bytes --]

 permalink raw reply	[|nested]
2023-01-16 23:47 [bitcoin-dev] OP_VAULT: a new vault proposal Andrew Chow
  2023-01-17  7:46 ` Anthony Towns

  
From: James O'Beirne @ 2023-01-20 17:43 UTC ( / )
  To: Andrew Chow, Bitcoin Protocol Discussion



Andrew, thanks for taking the time.

> It seems like this proposal will encourage address reuse for vaults,
> at least in some parts. It seems like it would not be difficult to
> ensure that each vault address was unique through the use of key
> derivation.

I think it's worth stepping back and noting that this proposal defers
the level of privacy-vs.-efficiency to be decided by the end user.

For users who are very privacy conscious and are doing fairly low volume
(a few vaulted coins a month?), trading the ability to do batched
operations for no privacy loss seems reasonable. They can use a ranged
descriptor to generate recovery paths and unvault sPKs and reveal no
marginal information during recoveries or unvaults.

Though of course there still may be an obvious temporal association
across transactions in the case of recovery - everything with the same
unvault key has to be recovered at once.

For users who expect to have large numbers of vaulted coins and maybe
don't care as much about privacy (e.g. many commercial users), revealing
the related nature of coins that are being unvaulted or recovered seems
like an okay cost to pay. Such users might decide to create "tranches"
of vaults with different parameters in whatever manner makes sense for
their use.

Importantly: in either case, you can always keep the nature of
still-vaulted coins hidden by burying the OP_VAULT script in a taptree
and varying the internal key you use for each coin with a ranged
descriptor. This way, only the revealed contents of unvaults and
recoveries can be associated. So I think that's your worst case, which
really doesn't seem bad to me.

As an aside, a goal in supporting address reuse wasn't for address reuse
*per se* - it was to remove potential catastrophe resulting from the
case where you give someone a vault address to deposit to, and they wind
up depositing to it multiple times - whether you warned them against it
or not.


> I'm not sure how [batching without privacy loss] could be solved
> though.

For recovery, I think it might be intractable at the moment.

Seemingly unrelated vaults which have the same recovery parameters will
presumably be recovered together if the unvault key is compromised. The
simple fact that these outputs are being spent together and are OP_VAULT
scripts fundamentally reveals their association during recovery, no way
around that AFAICT.

Similarly for the unvaulting, you can't get around revealing that you're
spending a group of outputs that contain an OP_VAULT script.

As mentioned above, unvaulting -- regardless of whether your vault
configuration supports batching or not -- *never* has to correlate
unvaulted coins to still-vaulted coins if you are either

  (i) varying the internal key used for the OP_VAULT taptrees, or
  (ii) using the optional "authorized recovery" feature and are varying
       that sPK with a ranged descriptor.

So there's really never a case where unvaults have to reveal a
relationship to, or between, still-vaulted coins. Subsequent unvaults
may be able to be correlated though on the basis of the recovery sPK
target hash.


> It just means that the recovery scripts must be the same, and this
> would leave an identifying mark on chain for every unvault.

This is only true if the user decides to create vaults which share
"literal" recovery paths. At the risk of belaboring the point for
clarity, you can avoid this by generating the different "literal"
recovery paths from a ranged descriptor which is controlled by a single
private key -- of course at the cost of no batched recovery.


> not to mention that sweeping to recovery would also reveal all of your
> coins too.

Maybe it's worth contextualizing that recovery sweeps really only happen
as a final fallback in catastrophic cases that, in a world without
vaults, would result in the coins simply being stolen. In this case I
would guess most users are willing to make the privacy trade to retain
ownership of their coins.


> I think it would be sufficient to do the same check as the OP_UNVAULT
> script [when validating the revault output in the unvault trigger
> transaction] and just require that the recovery script and the delay
> are the same

Consider that this allows the holder of the unvault key (possibly an
attacker) to immediately spend the vault into a new vault configuration
with a different unvault key.

Obviously the recovery path would still be the same, and so the owner of
the vault could still sweep if the unvault key switch was unauthorized,
but I'll need to think a little bit more about whether this change is
more fundamental.

Generally that would be easy to implement, though. I'll think on it. My
suspicion is that you're right and this would be a good change.


> I'm also not convinced that OP_VAULT and OP_UNVAULT should be allowed
> for bare and P2WSH outputs. It seems like it would make sense to just
> limit their usage to tapscripts as this would simply their
> implementation.

I think you're right, and accordingly I'll shortly be reimplementing
this with OP_SUCCESSx overrides instead of OP_NOPs. I'd be curious to
know if anyone has any objections to this - i.e. if there's a solid case
for vaults in a pre-taproot context.

James