Return-Path: Received: from smtp4.osuosl.org (smtp4.osuosl.org [IPv6:2605:bc80:3010::137]) by lists.linuxfoundation.org (Postfix) with ESMTP id C9216C002D for ; Tue, 10 Jan 2023 12:30:03 +0000 (UTC) Received: from localhost (localhost [127.0.0.1]) by smtp4.osuosl.org (Postfix) with ESMTP id 9FC3240A00 for ; Tue, 10 Jan 2023 12:30:03 +0000 (UTC) DKIM-Filter: OpenDKIM Filter v2.11.0 smtp4.osuosl.org 9FC3240A00 X-Virus-Scanned: amavisd-new at osuosl.org X-Spam-Flag: NO X-Spam-Score: 0.599 X-Spam-Level: X-Spam-Status: No, score=0.599 tagged_above=-999 required=5 tests=[BAYES_00=-1.9, LOTS_OF_MONEY=0.001, MONEY_NOHTML=2.499, SPF_HELO_PASS=-0.001, SPF_PASS=-0.001, UNPARSEABLE_RELAY=0.001] autolearn=no autolearn_force=no Received: from smtp4.osuosl.org ([127.0.0.1]) by localhost (smtp4.osuosl.org [127.0.0.1]) (amavisd-new, port 10024) with ESMTP id THI-r9--Uibk for ; Tue, 10 Jan 2023 12:30:02 +0000 (UTC) X-Greylist: from auto-whitelisted by SQLgrey-1.8.0 DKIM-Filter: OpenDKIM Filter v2.11.0 smtp4.osuosl.org 2DC3340985 Received: from azure.erisian.com.au (azure.erisian.com.au [172.104.61.193]) by smtp4.osuosl.org (Postfix) with ESMTPS id 2DC3340985 for ; Tue, 10 Jan 2023 12:30:02 +0000 (UTC) Received: from aj@azure.erisian.com.au (helo=sapphire.erisian.com.au) by azure.erisian.com.au with esmtpsa (Exim 4.92 #3 (Debian)) id 1pFDlN-0004Ak-Ol; Tue, 10 Jan 2023 22:29:58 +1000 Received: by sapphire.erisian.com.au (sSMTP sendmail emulation); Tue, 10 Jan 2023 22:29:52 +1000 Date: Tue, 10 Jan 2023 22:29:52 +1000 From: Anthony Towns To: James O'Beirne , Bitcoin Protocol Discussion Message-ID: References: MIME-Version: 1.0 Content-Type: text/plain; charset=us-ascii Content-Disposition: inline In-Reply-To: Subject: Re: [bitcoin-dev] OP_VAULT: a new vault proposal X-BeenThere: bitcoin-dev@lists.linuxfoundation.org X-Mailman-Version: 2.1.15 Precedence: list List-Id: Bitcoin Protocol Discussion List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Tue, 10 Jan 2023 12:30:03 -0000 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: <#recovery> ( <#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 ; verified by checking it the hash of the sPK matches <#recovery> and the amount it preserved unvaulting: spends to a scriptPubKey of <#recovery> ( <#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 " OP_CTV" with "<000..0> 0 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) 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