-
Notifications
You must be signed in to change notification settings - Fork 73
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
2-of-2 multisig spends without co-signer key constraint #153
Comments
Hi @joostjager, thanks for opening the issue! I'm currently exploring how to extend the capabilities of the app for use cases that aren't currently covered. I think it's a bit more complicated, and the answer to whether what you suggest is safe depends on how one defines the security model. With wallet policies, I'm aiming at strongly guaranteeing the segregation of accounts. That is, if you register a wallet policy with a certain name, the name is show when you spend from it, and it shouldn't be possible to get you to spend from that account/policy without you realizing (an exception is if you registered multiple policies with the same descriptor − or even with a different but overlapping descriptor, since policies with If you allow spending without registering a policy, the hardware wallet can no longer guarantee that you're not spending from one of the registered policies. So there is some design space here that would need to be explored, both functionally and in terms of UX:
Finally, another option is to make separate apps for specific use cases; that's not really viable today, but I'm hoping it will become a real possibility by next year once this project matures. |
Using the legacy app could perhaps be useful for some limited testing. Only, and I didn't mention this before, this 2-of-2 multisig lives in a tap leaf. So it would still require the latest app version to sign for that. I see your point about overlapping policies. The policy in the issue description would overlap with many other 2-of-2 policies because of the I should probably also add a bit more context on the particular multisig that I am looking at. The co-signer key is not part of a known HD wallet, because it is an ephemeral key. It is randomly generated, used to sign one particular transaction and discarded immediately. This simulates a convenant, not unlike for example the presigned htlc transactions in lightning (although those do not use an ephemeral key). Thanks for lining out part of the design space, it's helpful to build out my mental model of the ledger system. For #43, generalizing the policy would mean adding a symbol to represent any key? But wouldn't you still have the problem where an ad-hoc policy verification for any key would overlap with a registered, more narrow policy? At the very least, it seems some kind of warning associated with a catch-all key specifier would be appropriate. Regarding a less safe advanced signing mode - how would that work? I presume this won't be a persistent setting, but again some kind of token similar to the policy hmac? I also thought about a separate app. But even with enough memory available, it is still the question whether that code will as reliable and safe as the default btc app that many users use. Does any of the options stand out to you as better than the others? |
Not sure statelessness adds any substantial problem; I suppose what you want to do is simply to prevent using the same xpub in multiple policies; at this time, guaranteeing that's the case is the user's responsibility. I have ideas on how to enforce this in a stronger way (even in a stateless device), but not sure the benefits are worth the amount of work at this time.
I suppose we can imagine an extension of the wallet policy language to catch this: one could have a special wildcard placeholder, for example
In general, some advanced features could make sense for users who know what they are doing, but be difficult to explain to the average user. Therefore, at some point I might decide to disable some features by default, but allow people to enable them in the settings; in this way, you could let people use them without any warning (as they already took the decision of going to the settings to enable them).
In an app-streaming world, it will be easy to share code between apps, so I wouldn't be too worried about that.
At this time, I'm more interested in exploring in answering these questions:
Having an advanced signing mode that allows full inspection of inputs/outputs (possibly as a fallback) sounds like a good long-term plan. |
I was indeed thinking of checking for overlaps when a policy is verified, as you could do on a stateful device. But agree that the effort required may not be justifiable, assuming it is indeed possible on a stateless device. Curious though what those ideas to accomplish this are. Or are you referring to the reserved derivation path idea below?
This is also what I was thinking indeed. Or alternatively could it be a regular placeholder in the descriptor template, paired with a
Just to be clear, this setting would be written in non-volatile RAM right?
Now that you mention this, another restriction that is relevant for my use case is to specify key information for the outputs. So only allow spending to a specific wallet, but not necessarily the wallet that the device signs for. I suppose this does increase the ways in which signing policies can overlap.
I like this idea. Perhaps it can be combined with wallet policies too. Maybe this is what you meant, but I was thinking of something like "only allow @* in combination with non-standard derivation paths". This still allows the user to benefit from wallet policies, without overlapping with regular multisig wallets for which all keys are known in advance. |
Actually, you're right that it might not be possible while being fully stateless, but you can reduce it to a single hash by using an accumulator that can do proof-of-not-membership (e.g. sparse Merkle trees). One way to protect from the loss of state could be to use an external party to store the state, encrypted with a key derived from the seed. The external party is only trusted to store an encrypted blob, and sign "I updated the state from X to Y" when it changes. Anyway, not planning to experiment with these ideas until I have app-streaming to make them easy!
Indeed, using the wildcard in the keys information might be cleaner!
Yes, the apps can store things in the flash memory; the annoying limitation is that there is currently no way to persist this data in case of app or firmware upgrades.
Adding more HSM-like behaviors to the spending policies is indeed one of the long-term projects that I want to work on once I finish the re-implementation of the bitcoin app in app-streaming!
I definitely have to think about these more! I'm a bit hesitant to commit to any specific design choices until I get some more people to experiment with wallet policies (therefore, very useful that you showed up here!). One of the reasons I'm focusing on app-streaming is to make it a lot easier to experiment and iterate with more advanced stuff that is painful to implement in embedded C. |
I am wondering, wouldn't it have been more logical to define the derivation wildcards in the keys information vector instead of in the descriptor template? The idea being to have the whole pattern that defines the key space in one place (origin + xpub + path). So something like:
Then the placeholders can always be just The downside is probably that you may need to duplicate the key info in some cases with a different derivation path? Perhaps the more flexible option is to allow a derivation path both in the template and in the key information, which are then simply concatenated. |
This is actually how the first version of the language was designed, see the old docs. There are multiple downsides to that:
Flexibility means more complexity when you implement these things, which is especially problematic on tiny embedded hardware... Too much flexibility in descriptor (for no benefit in terms of use cases) is one of the main reasons I'm proposing a layer on top with wallet policies − but that's still open research, at this point. |
I see the challenge here, no doubt.
In a potential future with overlapping key spaces (such as with the
Isn't it the case that this layer on top is still constrained by what can be expressed on the wallet policy level? Or you mean implementing advanced policy in a streamed app? |
If all keys have a This breaks if you allow raw keys without the
I meant that wallet policies are a layer on top of descriptor where I remove flexibility (while aiming at not losing any useful functionality). While that's not the only reason I proposed wallet policies, I would probable not have been able to make miniscript work on Nano S without the added constraints. Being still the most-sold device by Ledger, I wasn't happy with leaving 3+ million devices without miniscript support. Streamed apps will definitely make it a lot easier to experiment as a playground for new features before I settle on a design and implement them in the C app. |
I think I didn't express myself clearly enough. What I wanted to suggest is to only check for uniqueness of the (derived) public keys at signing time. So even in case the xpubs or "any key" specifiers in the policy key information vector happen to overlap for some derivation, signing will only be denied if the actual derivation in the psbt leads to identical keys. Raw keys will also be included in this uniqueness check. I think that would address any malleability concerns without restricting the wallet policies that can be registered?
This is definitely appreciated! It indeed opens up lots of use cases. One use case I've been thinking about for example (unrelated to any-key-multisig) where miniscript support may be helpful is to sign for transactions that time-lock funds. |
Ah, I see! But I'd say that at that point it's too late to do that check: if you're spending, it means that funds where already sent to such a script, and there might not be any non-malleable way of spending them. My goal is to not even allow registering policies that are not safe (in miniscript sense) for every possible ( |
Good point. The device could be restricted to never generate such addresses in the first place given the wallet policy, but there could be another source of addresses that doesn't adhere to this constraint? I understand your design goals here. It does seem incompatible with my ephemeral-cosigner use case, unfortunately. Displaying a warning every time signing without a policy is attempted isn't great ux either. So I guess that leaves the persistent advanced signing setting option? Maybe it's not a bad idea. In this thread, we've been discussing the ephemeral signer case and tried to fit it into the wallet policy model. But tomorrow someone else may show up with something that is different again. The ability to configure the device to a less strict mode at least seems to cover many use cases at once and probably helps to keep those users on-board until a more generally usable policy system has materialized? |
Well, if an address is not generated/supported, it might be acceptable that you can't that spend it from the Ledger app.
It could also be that my requirements on forcing "non-malleable policies" are just too strict. If I understand the consequences better and there are good use cases, it would be reasonable to lift them.
Yes, that could be possible. Generally speaking, transactions with no change address might be much easier to handle with a reasonable notion of security, and it might be ok if it doesn't fit in the wallet policy model. Perhaps it's enough to make it clear in the UX if you're receiving to / spending from a wallet policy − not sure we'd really need an explicit setting. After all, wallet policies try to make the most general representation of an "account" (in the layer-1 sense), and not all UTXOs are meaningfully part of an "account". |
I'd like to re-emphasize that the use case for 2-of-2 multisig transactions with an ephemeral co-signer may be more general than it might seem at first glance. Convenants in bitcoin have been discussed for a long time and can be used to support various use cases. A notable use case is vaults where restrictions on unvault destinations are put in place upfront. In the absence of native bitcoin script support, presigned transactions currently seem to be the only way to implement these. Presigned transactions have security trade-offs of their own, but I think that hw wallet support helps to mitigate some of the concerns that may exist. Perhaps it is even a pre-requisite for people to start looking into this option at all.
For the 2-of-2-ephemeral use case, I think I'd be most happy if I could sign by just verifying external output(s) and fee on screen without any additional steps for the user to go through. I have to say that the concept of stateless wallet policies also makes me slightly nervous in a way. Somewhere in the back of my mind, I'd always be wondering if someone somewhere didn't register a rogue policy with the same name and attaches that to the signing requests. Could be the same ledger device that is briefly used by an attacker to do the registration. At that point, the policy is attached to the seed, so that wallet can be considered compromised in some sense. Is it a valid concern that the introduction of policies can make the device less secure? |
All that sounds great, but with the wide design space, I would rather experiment on these applications on app-streaming, so that it becomes easy to program a hardware wallet like an HSM.
That would be a concern with stateful as well, as far as I can tell. More generally, while further hardening is always possible, protecting the user from an attacker that has access to an unlocked device is outside of today's security model. Of course, multisig does already offer some level of protection from that, nonetheless (as you'd need to corrupt a quorum of signers/devices). Note that even if you register another policy with the same name, you couldn't be tricked into spending from one policy and sending the change address to a different one. |
It is an interesting idea, but it seems to come with extra complexity and potential risks too. For the convenant use cases, I am not sure if it is worth it compared to a simple form of wallet policy bypass on the existing app.
Yes, but with stateful at least you can verify what is registered on a particular device? Stateless an attacker could use another device loaded with the same seed to authorize the policy, and there wouldn't be any way to find out. You could be generating addresses and signing transactions for a long time without noticing that something is wrong.
Manipulating receiving addresses is something that definitely concerns me. And a software wallet being compromised is a real possibility and the whole reason for using a hardware wallet in the first place. As a user, I'd like to avoid these scenarios. But it seems there is no way to opt out of this currently for multi-sig transaction signing. |
I added a comment to #43, it's certainly an interesting direction for an enhancement!
Let me rephrase: you need to both have your software wallet compromised by the attacker, and they need access to an unlocked device with the same seed. In a setting that is so high-value that such a sophisticated attack is a concern, one can increase security with a higher multisig quorum, distributing the signing devices (with different seeds) in multiple locations, avoiding putting the same seed on multiple devices, etc. In the case of multisig, this attack needs to be repeated on a quorum of signers (assuming they correctly follow the procedures and verify the receiving address on the hardware wallet screen − otherwise you're inherently reducing the security of your multisig setup, and there's nothing that the hardware wallet can do). I agree that there are ways of hardening this more for some scenarios, but I'm not comfortable exploring variations on the theme when I still didn't even get enough comments on the Wallet policies BIP proposal. My primary concern at this time is making multisig and miniscript adoption faster by making them easy to use, with a level of security that is reasonable for the typical user. |
Very much appreciated!
Yes, those kinds of measures would surely help. But I also think it is important to acknowledge that, strictly speaking, stateless policies are not as secure as policies that are saved on the device - even if the difference may not be significant in most real use cases.
Added a few comments taken from this discussion in there. Maybe it triggers some thoughts with the people following the bip. |
I think this cannot be judged in absolute sense, as it also depends on how the rest of the user's security is implemented. For example, if "stateful" means that you can unlock the device and inspect what policies are registered in that device, this means that if someone gets access to the unlocked device, they can learn about your registered policies. With a stateless approach, you can take additional measures (like putting the software wallet in a hidden partition, and have decoy wallets in clear). My assessment is that the difference in security is not that big either way, and I expect the stateless approach to offer the best UX with a proper integration. In practice, bad UX tends to result in worse security in practice, as people are more easily going to make mistakes. |
While it's true that policy information can potentially be leaked in a stateful scenario, it's worth noting that this is not as dangerous as the scenario where an attacker is able to get the device to sign transactions. The policies merely guide the behavior of the device; they don't grant direct access to the assets controlled by the device. In essence, while a leak of policy data is definitely not ideal, it's a lesser concern when compared to the risk of transaction signing. As for the ephemeral hmac approach, while it does provide an additional layer of security by binding the secret to the device rather than the seed, it's important to underline that it still does not make a policy revokable. While the ephemeral hmac can prevent someone from copying the policy onto another device, once the policy is established, it remains in effect until the ephemeral hmac is removed. In terms of managing individual policies, one could theoretically assign a unique hmac to each policy and allow for the deletion of a specific policy. However, this starts to complicate the system and veers towards a stateful approach. |
Centralizing the discussion for this and other issues in #210. |
I am investigating the possibility of signing transactions of the following type using a ledger device:
multi(2, @0, <any>)
-> external destinationThe user would only verify the external destination on the screen.
@0
is the ledger device key. The co-signer key<any>
is any key, no restrictions. Because there are no outputs to a multisig address, it seems the usual security concerns do not apply here.I tend to think that this shouldn't require a registered wallet policy, but is that possible with the current version of
app-bitcoin-new
?The text was updated successfully, but these errors were encountered: