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

Sign transactions not fitting the wallet policy approach #210

Open
bigspider opened this issue Nov 27, 2023 · 10 comments
Open

Sign transactions not fitting the wallet policy approach #210

bigspider opened this issue Nov 27, 2023 · 10 comments
Labels
enhancement New feature or request

Comments

@bigspider
Copy link
Collaborator

bigspider commented Nov 27, 2023

A number of issues were opened to request signing in cases that don't fit the wallet policy model:

This issue tries to generalize the problem. How do we safely sign transactions outside of the model of wallet policies?

Wallet policies

Wallet policies build on top of Output Script Descriptors to model in a sound way what software wallets typically call an account.

Whether single-sig, multisig, musig, miniscript, etc, for most software wallets, an account is exactly: a list of receiving addresses, plus a list of change addresses.

Wallet policies in hardware signers

Wallet policies are a first class citizen in the Ledger bitcoin app (and similar specs have now been implemented in the BitBox02, and in Blockstream Jade via the Wally library).

The signing API is currently similar to sign(psbt, wallet_policy) (plus the wallet_policy_hmac, not relevant for this discussion), which means that the "wallet policy" is additional information on top of just the transaction being signed, represented in the psbt. (in principle, the wallet policy itself could be added to the psbt; however, there is no standard for it).

Having precise knowledge of "what account" you are receiving to, or spending from, allows to strengthen the security model quite substantially, as malware in the in the software wallet can't tamper with that information. In practice, the expectation is that people will have multiple accounts, and many of them will involve multiple keys (other co-signers). Wallet policies allow you to know exactly from which account you're spending. That's important: if you have a 2-of-2 with your wife, and another 2-of-2 with your colleague at work, it's not enough to know that you're "spending 0.1 btc and sending it to bc1pxxx..", you want to know that you're "spending 0.1 from your work account and sending it to bc1pxxx". Without that, if your co-worker gets access to your computer, it's trivial for them to install malware to trick you to spend your wife's money when you think you're using the company account. Of course, this inherently requires full knowledge of the script on the hardware signer's end, including all of the cosigner's xpuOne would also have to think abs.
With wallet policies, the device can inform you that you're spending from the account named Cold storage (for example):

spend-wallet-policy

The hardware signer can use the information in the psbt, plus the knowledge of the wallet policy, to:

  • make sure that the change output is going to the right place [*];
  • inform what account you're receving to / spending from;
  • possibly, simplify the UX for known outputs. For example, if you know that you're sending to another of your accounts (or even some known external account), its descriptive name could be shown along – or instead – of the output address); this is not currently implemented.

In other words: wallet policies allow strict account segregation: the hardware device can guarantee that you're not touching any other account (as long as you take care to register different policies under different names, of course).

[*] In principle, before taproot, this doesn't strictly require wallet policies, as the descriptor could be "decompiled" from the witnessScript; this is no longer true in taproot trees, however, as the [PSBT](https://github.com/bitcoin/bips/blob/master/bip-0174.mediawiki) does not contain enough information (knowledge of how to generate the entire taptree, for any address index, is necessary).

Safe signing outside of wallet policies

While wallet policies themselves could possibly be generalized to cover some use cases that are not currently represented by the BIP proposal, it's clear that there are other situations that just don't fit in this model of signing for an account: not all UTXOs are meaningfully part of an account as it can be represented in wallet policies.

UX considerations

Allowing signing behaviors that go outside the model of wallet policies would likely break the strong account segregation guarantee above.

That might be fine, as long as it doesn't happen seamlessly: the user must know that they are stepping somewhat outside of the safe zone. Possible options:

  • a prominent warning with explicit user confirmation required
  • signing for such transaction must be explicitly enabled in the settings of the app (1 time action where the user can properly be warned).

Another option that could be worth investigating is to attach the "unsafe signing behavior" to specific keys. That is, one could register a special kind of "wallet policy" (to be defined) that means "for these internal xpubs, allow unsafe signing". That would allow to keep the strong account segregation guarantees for any other xpubs, as long as the user is diligent in not reusing xpubs for different purposes. The downside is, of course, that software walltes would need to be aware and explicitly opt-in to this approach, which makes integrations somewhat harder.

What's safe to sign, if you don't have wallet policies?

This is to be defined, and knowing the desired use cases would help. (Note that this is about finding general approaches to signing that work for many use cases; nothing works for all use cases, and for specific applications, a separated customized app would be a better approach)

For transactions signed with SIGHASH_ALL, one case that would generally be safe (apart from not having the strong account segregation property mentioned above) is if:

  • there is no change output (all outputs are shown to and verified by the user)
  • all inputs are assumed to be internal.
@bigspider bigspider added the enhancement New feature or request label Nov 27, 2023
@bigspider bigspider changed the title Sign transactions not fitting within wallet policies Sign transactions not fitting the wallet policy approach Nov 27, 2023
@fess-v
Copy link

fess-v commented Dec 6, 2023

@bigspider first of all, thanks for this issue, really waiting for this possibility to be opened
Regarding taproot trees - won’t it be possible to pass the whole taproot tree construction with normal public keys (not xpubs)? What is the disadvantage of this approach in comparison to other methods like unsafe confirmations and so on?

@bigspider
Copy link
Collaborator Author

@bigspider first of all, thanks for this issue, really waiting for this possibility to be opened Regarding taproot trees - won’t it be possible to pass the whole taproot tree construction with normal public keys (not xpubs)?

In wallet policies, the restriction of having ranged xpubs (with distinct derivations) is explicitly kept even across leaves.
While it might not seem as bad as in segwit scripts (where it messes with miniscript non-malleability guarantees, for example), the key reuse problem is still there: if you receive on multiple addresses of the same wallet account, and then you want to spend these different UTXOs using a leaf that contains a fixed pubkey, you will reveal the pubkey, allowing an external observer to trivially correlate all those spends.

Generating pubkeys is cheap, so an external co-signer that is able to provide a fixed pubkey can easily be generalized to instead provide an xpub, which solves the key-reuse problem.

Of course, this is not an issue if instead of a "wallet account" (that can receive/send many transactions), you have a single UTXO, that you will spend entirely without a change address; but that's not the scope of wallet policies.

What is the disadvantage of this approach in comparison to other methods like unsafe confirmations and so on?

Not sure I understand what you mean by "other methods like unsafe confirmations".

@fess-v
Copy link

fess-v commented Dec 8, 2023

@bigspider thanks a lot for your reply, now it's clear
One question in this case appears
We're using taproot leaf scripts (told above), but not using an internal public key at all. The internal public key is still important for the whole taptree construction. Instead of an internal public key, we generate a MuSig public key with all owners merged, which makes it even harder for us to provide xpubs even if we convince all other wallet developers to provide it on connection, which is at least possible.
What is the best way to handle it with the current Ledger wallet policy? Thank you again

P.s. in this case we don't care about revealing this Internal pubkey to anyone since it is not being used, but we really bother about not having a real internal public key which could've spent everything from the multisig wallet.

@bigspider
Copy link
Collaborator Author

bigspider commented Dec 11, 2023

@bigspider thanks a lot for your reply, now it's clear One question in this case appears We're using taproot leaf scripts (told above), but not using an internal public key at all. The internal public key is still important for the whole taptree construction. Instead of an internal public key, we generate a MuSig public key with all owners merged, which makes it even harder for us to provide xpubs even if we convince all other wallet developers to provide it on connection, which is at least possible. What is the best way to handle it with the current Ledger wallet policy? Thank you again

P.s. in this case we don't care about revealing this Internal pubkey to anyone since it is not being used, but we really bother about not having a real internal public key which could've spent everything from the multisig wallet.

In theory, using the syntax for the musig() key expressions drafted here, it would be possible to take a descriptor like

tr(musig(xpub1, xpub2,xpub3)/<0;1>/*, taptree)

and replace it with:

tr(aggr_xpub/<0;1>/*, taptree)

where aggr_xpub is musig(xpub1, xpub2,xpub3). The Ledger app is of course not able to participate to the musig signing as of yet.

Instead, if you want the public key path to really be unspendable, it is not difficult to produce an xpub such that there is (provably) no private key. Then you can use tr(unspendable_xpub/<0;1>/*, taptree).
Of course, the Ledger app doesn't know that this xpub is unspendable, and will therefore show it among the other xpubs during registration; that's not great in terms of UX, but it works.

This has security implications, though: the fact the the aggregate/unspendable xpub is produced in a certain way is not verifiable from the device, so additional measures should be in place in the software wallet to guarantee that the xpub is not replaced with some attacker's xpub (for example, by malware on the user's machine).

The appropriate fix would be to define a key expression for the descriptor language that describes an unspendable key. Then you could do something like:

tr(unspendable(something)/<0;1>/*, taptree)

Once such a standard exists, the UX could be simplified somewhat, as the fact that the key generated by unspendable(something) is indeed unspendable can be verified by the device.

@fess-v
Copy link

fess-v commented Dec 11, 2023

@bigspider thanks so much for such a detailed explanation!
Yeah, the second approach looks perfect, if we can make this descriptor! We will start implementing that and reaching out to other wallet teams to add xPUBs on connection. Once it's done and working, we can work on the specific unspendable descriptor.
We will move from MuSig to unspendable xPUBs as well.

@fess-v
Copy link

fess-v commented Dec 11, 2023

@bigspider another point to consider
currently, all extension wallets (at least extensions) use the same account xpub to generate all public keys and addresses, and in this case, users won't likely want to reveal this xpub to applications considering the same privacy concerns, which is even more severe than linking public keys on signatures, since every single address will be revealed.
What is your opinion on that? Except for changing the overall wallet address deriving flow, which is unlikely ever gonna work for already established wallets

@bigspider
Copy link
Collaborator Author

@fess-v could you describe the kind of descriptors and/or signing flow that such extension wallets are using? I'm not familiar so it's hard to give an informed reply.

@fess-v
Copy link

fess-v commented Dec 11, 2023

@bigspider these wallets do not have a similar thing to the wallet policy.
Normal flow with separate Bitcoin applications includes:

  1. Wallet connection - an application calls the connection method, the wallet opens a modal, then the user selects his account which he wants to connect (just an address basically, not a separate account with a separate xPUB), an application receives a public key and an address
  2. Message signature - an application calls the extension wallet and passes the public key to sign with and a message
  3. Transaction signature - an application passes a PSBT to sign, an account with which to sign (an address or a simple public key), and an inputs array which the application wants to get a signature for. After that, this PSBT is parsed on the wallet side and displays all the inputs and outputs and corresponding addresses. Then the user gives his approval and the signed PSBT is returned to the application, which is used when enough signatures are collected.

Btw this flow is the same for all bitcoin applications, not only our multisig, like marketplaces, ordinals and brc20s related projects, etc.
More information - https://leather.gitbook.io/developers/bitcoin/sign-transactions/partially-signed-bitcoin-transactions-psbts, https://docs.xverse.app/sats-connect, https://docs.unisat.io/dev/unisat-developer-service/unisat-wallet

@bigspider
Copy link
Collaborator Author

Not sure I understand; if you're not willing to reveal the descriptor, likewise you're not willing to reveal the wallet policy.

If the limitation is because some systems are designed around address reuse, unfortunately wallet policies specifically want to not allow that, or at least not incentivize it.

@fess-v
Copy link

fess-v commented Dec 11, 2023

@bigspider got it. In this case, I think the only option to integrate Ledger to such Bitcoin apps mixed with other wallets would be to prepare something outside of Wallet Policies as you described in the issue.
signing for such transaction must be explicitly enabled in the settings of the app - I vote for something like this since it is similar to the same process in the Ethereum application and will open the ledger application to the new emerging apps ecosystem.
Would be great to have a similar interface as web-based wallets do - so apps can pass indexes of inputs to sign, psbt, and the wallet with which to sign (pub key, xpub, address - IMO not that important for applications in this case)

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

No branches or pull requests

2 participants