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 _______________________________________________ bitcoin-dev mailing list bitcoin-dev@lists.linuxfoundation.org https://lists.linuxfoundation.org/mailman/listinfo/bitcoin-dev