Skip to content
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

Closed
joostjager opened this issue Apr 25, 2023 · 21 comments
Closed

2-of-2 multisig spends without co-signer key constraint #153

joostjager opened this issue Apr 25, 2023 · 21 comments

Comments

@joostjager
Copy link

joostjager commented Apr 25, 2023

I am investigating the possibility of signing transactions of the following type using a ledger device:

multi(2, @0, <any>) -> external destination

The 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?

@bigspider
Copy link
Collaborator

bigspider commented Apr 25, 2023

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.
Indeed, the current app wouldn't allow signing for those wallets (might work with the legacy app, which is still possible to install − although support for multisig was always meh on both the app and the client libraries...).

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 multi/sortedmulti and identical keypaths share a subset of addresses... but one could argue in that case that the user has already messed up by registering those policies).

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:

  • allow spending without registering policies, but show the descriptor template and cosigners (see also: Allow signing and showing addresses without registering policies, or without policies at all #43). This would also require generalizing wallet policies, as currently all placeholder must be of the shape @0/** or @0/<M;N>/*;
  • add an advanced setting to enable signing with looser security measures, with no change detection, and figure out different rulesets for when that's to be considered safe (or "you're on your own mode": will just sign all inputs with whichever internal keys are claimed in the PSBT);
  • same as the previous one, but without the advanced setting: just give a visible warning to the user;
  • have an advanced signing mode where all inputs and all outputs can be inspected (this one is tricky in terms of UX, I guess − esp. on small screens).

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.

@joostjager
Copy link
Author

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 <any> key. You could argue though that this can also be classified as 'user messing up'. Too bad that it seems impossible to do any kind of overlap check because the device is stateless.

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?

@bigspider
Copy link
Collaborator

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 <any> key. You could argue though that this can also be classified as 'user messing up'. Too bad that it seems impossible to do any kind of overlap check because the device is stateless.

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 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).

I suppose we can imagine an extension of the wallet policy language to catch this: one could have a special wildcard placeholder, for example @* which means: "any key goes" (where the wildcard is inherently assumed external). So the policy above might be a taptree containing multi_a(2, @0/**,@*) in a leaf. The algorithm to match the generated script would then have to be generalized correctly handle the wildcard keys, but this doesn't seem too hard to handle.

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?

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).

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.

In an app-streaming world, it will be easy to share code between apps, so I wouldn't be too worried about that.

Does any of the options stand out to you as better than the others?

At this time, I'm more interested in exploring in answering these questions:

  • can we generalize wallet policies to cover all possible use cases?
  • if not, how to generalize signing without tampering with security guarantees of wallet policies? (e.g.: reserve some derivation paths for non-wallet-policy use cases − that prevents overlap!)

Having an advanced signing mode that allows full inspection of inputs/outputs (possibly as a fallback) sounds like a good long-term plan.

@joostjager
Copy link
Author

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 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?

I suppose we can imagine an extension of the wallet policy language to catch this: one could have a special wildcard placeholder, for example @* which means: "any key goes" (where the wildcard is inherently assumed external). So the policy above might be a taptree containing multi_a(2, @0/**,@*) in a leaf. The algorithm to match the generated script would then have to be generalized correctly handle the wildcard keys, but this doesn't seem too hard to handle.

This is also what I was thinking indeed. Or alternatively could it be a regular placeholder in the descriptor template, paired with a * in the keys information vector?

Therefore, at some point I might decide to disable some features by default, but allow people to enable them in the settings

Just to be clear, this setting would be written in non-volatile RAM right?

can we generalize wallet policies to cover all possible use cases?

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.

how to generalize signing without tampering with security guarantees of wallet policies? (e.g.: reserve some derivation paths for non-wallet-policy use cases − that prevents 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.

@bigspider
Copy link
Collaborator

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?

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!

This is also what I was thinking indeed. Or alternatively could it be a regular placeholder in the descriptor template, paired with a * in the keys information vector?

Indeed, using the wildcard in the keys information might be cleaner!
One slight complication is that it seems clear that I can't keep the requirement that key placeholders are always followed by /** or /<M;N>/*, which was a nice simplification as it implies all BIP32-paths in the psbt must end with the same /change/address_index.

Therefore, at some point I might decide to disable some features by default, but allow people to enable them in the settings

Just to be clear, this setting would be written in non-volatile RAM right?

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.

can we generalize wallet policies to cover all possible use cases?

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.

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 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.

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.

@joostjager
Copy link
Author

joostjager commented Apr 26, 2023

Indeed, using the wildcard in the keys information might be cleaner!
One slight complication is that it seems clear that I can't keep the requirement that key placeholders are always followed by /** or /<M;N>/*, which was a nice simplification as it implies all BIP32-paths in the psbt must end with the same /change/address_index.

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:

[d34db33f/44'/0'/0']xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/**

Then the placeholders can always be just @i.

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.

@bigspider
Copy link
Collaborator

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).

This is actually how the first version of the language was designed, see the old docs.

There are multiple downsides to that:

  • miniscript requires that keys are not reused in different places. By only putting an xpub it's easy to enforce that: I just check that all the xpubs in the key information are indeed different (this uses the assumption that all keys are then derived with a constrained structure that avoid overlaps, which is part of the reason why I don't allow arbitrary derivations). Note that this is a security-critical check for miniscript, as key reuse in the policy can cause malleability.
  • as you said, if you need the same xpubs with different derivation paths, you would like not to repeat that. The blowup in descriptor size is potentially quadratic for very reasonable policies with taptrees, as I argued in the BIP proposal.
  • logically, all the unhardened children of the same xpub could be considered part of the same "identity" in a policy. This allows to check just one xpub for each participant in the policy when you are registering the wallet, which feels like it's the natural thing to do.

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.

Flexibility means more complexity when you implement these things, which is especially problematic on tiny embedded hardware...
For example, checking that there are no duplicated xpubs would become a lot more complicated, as I mentioned above.

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.

@joostjager
Copy link
Author

I see the challenge here, no doubt.

miniscript requires that keys are not reused in different places. By only putting an xpub it's easy to enforce that: I just check that all the xpubs in the key information are indeed different (this uses the assumption that all keys are then derived with a constrained structure that avoid overlaps, which is part of the reason why I don't allow arbitrary derivations). Note that this is a security-critical check for miniscript, as key reuse in the policy can cause malleability.

In a potential future with overlapping key spaces (such as with the * key spec), this may be solved through a unique check on the fully derived public keys?

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.

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?

@bigspider
Copy link
Collaborator

In a potential future with overlapping key spaces (such as with the * key spec), this may be solved through a unique check on the fully derived public keys?

If all keys have a *, then yes, one could replace the * with 0 and check the derived keys. The check would then have to be repeated twice as for each <M;N> you want to try both choices (that is, always the first, or always the second for each such expression).

This breaks if you allow raw keys without the * operator, though, as one of the keys could be equal to some other xpub/0/37987983.
Not sure how bad that would be − perhaps it's acceptable, but I haven't put enough thoughts to it. As far as I can tell, the only risk is that some cosigner could exploit that to enable some malleability attacks that are otherwise impossible.

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.

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?

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.
But yeah... some things will be a lot easier if/when we stop supporting Nano S!
The app currently does everything while never keeping in memory more than 2 pubkey of the policy at the same time. :)

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.

@joostjager
Copy link
Author

joostjager commented Apr 28, 2023

If all keys have a *, then yes, one could replace the * with 0 and check the derived keys. The check would then have to be repeated twice as for each <M;N> you want to try both choices (that is, always the first, or always the second for each such expression).

This breaks if you allow raw keys without the * operator, though, as one of the keys could be equal to some other xpub/0/37987983.
Not sure how bad that would be − perhaps it's acceptable, but I haven't put enough thoughts to it. As far as I can tell, the only risk is that some cosigner could exploit that to enable some malleability attacks that are otherwise impossible.

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?

Being still the most-sold device by Ledger, I wasn't happy with leaving 3+ million devices without miniscript support.

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.

@bigspider
Copy link
Collaborator

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?

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 (change, address_index) choice.

@joostjager
Copy link
Author

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?

@bigspider
Copy link
Collaborator

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?

Well, if an address is not generated/supported, it might be acceptable that you can't that spend it from the Ledger app.

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.

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.
In general, I've probably been overly paranoid as the default choice.

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?

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".

@joostjager
Copy link
Author

joostjager commented May 1, 2023

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.

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.

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?

@bigspider
Copy link
Collaborator

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.

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.

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?

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.
You could of course be tricked into using a receiving address of the other policy (one thing I'd like to add in the long term is the possibility to inspect at any time the policy, not just its name), but these attacks also require compromising your software wallet.

@joostjager
Copy link
Author

joostjager commented May 2, 2023

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.

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.

That would be a concern with stateful as well, as far as I can tell.

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.

You could of course be tricked into using a receiving address of the other policy (one thing I'd like to add in the long term is the possibility to inspect at any time the policy, not just its name), but these attacks also require compromising your software wallet.

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.

@bigspider
Copy link
Collaborator

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.

I added a comment to #43, it's certainly an interesting direction for an enhancement!
After this discussion, I tend to believe that there is no sensible way to generalize wallet policies to "all possible use cases", so I will definitely look more into this.

That would be a concern with stateful as well, as far as I can tell.

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.

You could of course be tricked into using a receiving address of the other policy (one thing I'd like to add in the long term is the possibility to inspect at any time the policy, not just its name), but these attacks also require compromising your software wallet.

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.

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.

@joostjager
Copy link
Author

After this discussion, I tend to believe that there is no sensible way to generalize wallet policies to "all possible use cases", so I will definitely look more into this.

Very much appreciated!

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.

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.

I'm not comfortable exploring variations on the theme when I still didn't even get enough comments on the bitcoin/bips#1389.

Added a few comments taken from this discussion in there. Maybe it triggers some thoughts with the people following the bip.

@bigspider
Copy link
Collaborator

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.

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).
You could avoid this problem a more ephemeral hmac (that is: using a random secret stored on the device rather than a deterministic key from the seed). So it's still almost stateless, but the secret is device-bound rather than seed-bound. It might be useful to offer this option at some point if there's demand for it.

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.
I had positive feedback on this from folks at Unchained, who are storing the descriptor and the hmac for their clients (in fact, they would like the "stateless registration" approach to be standardized, so it could even work across vendors). It will be interesting to see what others think once more people integrate with it.

@joostjager
Copy link
Author

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.

@bigspider
Copy link
Collaborator

Centralizing the discussion for this and other issues in #210.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants