buidl-bitcoin/buidl-python

PSBT.parse() always thinks PSBT's TX obj is mainnet

Closed this issue · 12 comments

Example:

from buidl.psbt import PSBT

PSBT_B64 = "cHNidP8BAH0CAAAAAfsWeOlaFJKfqlQHzx+WgjRJlEB9XLzMCXchCw4si7PDAQAAAAD/////AoUIAAAAAAAAIgAgxb6HvJsx6G8mjBf/ERAtVkJHNNu5n0t6JaZr54V3Og7WHQAAAAAAABYAFMINV2dNtzP+jUjqf2bIwsPrqp+7AAAAAAABASsQJwAAAAAAACIAIDuuQfOewCl82IjzKJAjQy5pUQKaE7HBtEJA5pYh/y54AQWLUSECOLBbCCUjX61VtXIa9P1ZPhpsz0okfHLI8lQM/2dlnQshA1gkYwW4I1IawxzVo8L53yV1y/R7Jq3aeypId5KPdevNIQOv86ZXTRchmJ8w5IXJ9Vx18yQxUytq3xkZdCBKn4vTgiEDsT5favW6RBtmK7GQJ3HBl3p4AXDoJU6HAYrl9XXna/1UriIGAjiwWwglI1+tVbVyGvT9WT4abM9KJHxyyPJUDP9nZZ0LHBKYDu0wAACAAQAAgAAAAIACAACAAAAAAAcAAAAiBgNYJGMFuCNSGsMc1aPC+d8ldcv0eyat2nsqSHeSj3XrzRz30ECQMAAAgAEAAIAAAACAAgAAgAAAAAAHAAAAIgYDr/OmV00XIZifMOSFyfVcdfMkMVMrat8ZGXQgSp+L04Icx9BkijAAAIABAACAAAAAgAIAAIAAAAAABwAAACIGA7E+X2r1ukQbZiuxkCdxwZd6eAFw6CVOhwGK5fV152v9HDpStc0wAACAAQAAgAAAAIACAACAAAAAAAcAAAAAAQGLUSECPVlulR9WOiDFRX7p3cyIGDbmMn0qBJA0WHIRc6PWFlwhAskkzwGB+E7aT3ibDWIVgqmC1VprCHUq4OMVlzKJTZAdIQLfBhkEe8XmHQSm0vTSu2XZEQu58w9z4cBm7pzF5HC0gCED00gxYCXN/UhH6szbR+Ydq5+gxZN45Am/ruggbkUuSLRUriICAj1ZbpUfVjogxUV+6d3MiBg25jJ9KgSQNFhyEXOj1hZcHPfQQJAwAACAAQAAgAAAAIACAACAAQAAAAIAAAAiAgLJJM8BgfhO2k94mw1iFYKpgtVaawh1KuDjFZcyiU2QHRw6UrXNMAAAgAEAAIAAAACAAgAAgAEAAAACAAAAIgIC3wYZBHvF5h0EptL00rtl2RELufMPc+HAZu6cxeRwtIAcEpgO7TAAAIABAACAAAAAgAIAAIABAAAAAgAAACICA9NIMWAlzf1IR+rM20fmHaufoMWTeOQJv67oIG5FLki0HMfQZIowAACAAQAAgAAAAIACAACAAQAAAAIAAAAAAA=="

psbt_obj = PSBT.parse_base64(PSBT_B64)
print(psbt_obj.tx_obj.testnet)  # returns False but should be True

Screen Shot 2020-11-01 at 12 45 17 PM

So this is a tricky topic because it's hard to tell whether a PSBT is testnet or not.

So there's no flag in the PSBT. It can be implied from an included xpub or the path of the pubkey, but that means hardcoding certain paths as always being testnet. 84'/1', 44'/1', 48'/1', for example. This is pretty ugly and makes assumptions about how PSBT has to get used, but that's about the only reasonable way to determine it.

cc: @achow101 This is why I wanted a testnet flag in the PSBT spec.

Why would a PSBT even have a testnet attribute? What exactly is it being used for?

Raw transactions don't have a testnet attribute either, so just do the same handling you do for those?

Why would a PSBT even have a testnet attribute? What exactly is it being used for?

@achow101 I've written a PSBT CLI multisig wallet and I highly recommend people practice on testnet. If there's no way to infer whether a TX is on testnet or mainnet, the wallet either has to ask the user (annoying UI and users will occasionally mess up) or else it could display bitcoin addresses in a confusing/incorrect way (do you want to send X BTC to bc1...? vs do you want to send X BTC to tb1...)?

Right now I'm just asking the user to confirm their network, which is obviously not ideal:

buidl-python/multiwallet.py

Lines 431 to 433 in 4204afb

IS_TESTNET = not _get_bool(
prompt="Use Mainnet?", default=False
) # FIXME: can we infer this from the PSBT?

Why would a PSBT even have a testnet attribute? What exactly is it being used for?

Practically speaking, you need to display an address to the user to validate that the transaction sends funds to the correct place. For offline wallets, there is no easy way to know that what you're signing is testnet or not.

Raw transactions don't have a testnet attribute either, so just do the same handling you do for those?

This leads to hardware wallets having to be "testnet" mode or "mainnet" mode. Ledger, for example, I think always displays a mainnet address. Coldcard has to be switched to testnet or mainnet manually. This is not a good user experience and can't be fixed easily without something within the protocol.

@achow101 I've written a PSBT CLI multisig wallet and I highly recommend people practice on testnet. If there's no way to infer whether a TX is on testnet or mainnet, the wallet either has to ask the user (annoying UI and users will occasionally mess up) or else it could display bitcoin addresses in a confusing/incorrect way (do you want to send X BTC to bc1...? vs do you want to send X BTC to tb1...)?

Right now I'm just asking the user to confirm their network, which is obviously not ideal:

But you still have to ask them to know what kind of addresses to give out, so adding a testnet field to a PSBT wouldn't change that.

Ledger, for example, I think always displays a mainnet address.

Ledger has a Testnet app.

This leads to hardware wallets having to be "testnet" mode or "mainnet" mode. ... Coldcard has to be switched to testnet or mainnet manually. This is not a good user experience and can't be fixed easily without something within the protocol.

I don't think it really degrades the user experience that much. Furthermore, I think that having to switch to a testnet mode (or to a mainnet mode) is more secure; the user has to acknowledge that they are using a different network. Having the PSBT automatically make the wallet switch networks would make things more confusing IMO, and potentially make it easier to trick users into sending money they did not intend to send.

But you still have to ask them to know what kind of addresses to give out, so adding a testnet field to a PSBT wouldn't change that.

@achow101, what do you mean by that? I'm inferring the address type from the script type so I don't have to ask the user anything:
https://github.com/buidl-bitcoin/buidl-python/blob/4204afb1f67dac06cf1963f0ddf9991d656f10fc/buidl/script.py
(grep the page for def address( and you'll see each class has a corresponding address method whose only argument is whether or not we're on testnet)

I don't think it really degrades the user experience that much. Furthermore, I think that having to switch to a testnet mode (or to a mainnet mode) is more secure; the user has to acknowledge that they are using a different network. Having the PSBT automatically make the wallet switch networks would make things more confusing IMO, and potentially make it easier to trick users into sending money they did not intend to send.

As a developer I can tell you, it's a sucky experience, and those are your main users. There are workarounds, but they're complicated and awful if you're considering something outside a strict hardware wallet. Anyway, just wanted to let you know that not having the flag has real consequences to developers and it impedes implementation because it's annoying to develop with.

@achow101, what do you mean by that? I'm inferring the address type from the script type so I don't have to ask the user anything:
https://github.com/buidl-bitcoin/buidl-python/blob/4204afb1f67dac06cf1963f0ddf9991d656f10fc/buidl/script.py
(grep the page for def address( and you'll see each class has a corresponding address method whose only argument is whether or not we're on testnet)

What I mean is that in order for you to set that argument, you have to get the testnet information from somewhere, presumably the user. You have to know whether you are in testnet in order to give an address to the user. Having a testnet flag in PSBT won't change that.

What I mean is that in order for you to set that argument, you have to get the testnet information from somewhere, presumably the user. You have to know whether you are in testnet in order to give an address to the user. Having a testnet flag in PSBT won't change that.

@achow101 we're going back and forth on the same point and I think we're miscommunicating :(

If I had a testnet flag in the PSBT, then the wallet would have the transaction inputs/outputs and the knowledge of whether the TX was testnet or not, which is everything it would need to display the address (and amounts) to the end user. This is the opposite of your statement, what am I missing?

It's true that at some point during constructing the transaction the user needs to set their network, but realistically their online software is already doing this for them as you can't build a transaction without specifying a destination address (which it will validate and be certain of the network).

I don't see how this helps end-users at all, and it adds confusion. If they're participating in a 3-of-5 multisig (already quite a stressful process!), why is asking them on all 3 devices what network they're on a good thing? It's inherently confusing, I could even see an end user wanting to send a "test" transaction to a bc1... address! Obviously, as developer I like a nice interface, but the issue I'm trying to solve is for end-users.

It's true that at some point during constructing the transaction the user needs to set their network, but realistically their online software is already doing this for them as you can't build a transaction without specifying a destination address (which it will validate and be certain of the network).

That's my point. Why can't you use the setting from that time during signing? I get that the software is supposed to be stateless, but istm it's only stateless in that nothing is stored to disk. So there is state within a session, no? And within a session you can both sign a PSBT and get new addresses, so you will need to have the network setting in order to support the latter.

why is asking them on all 3 devices what network they're on a good thing?

Because multisig isn't just a single user protocol. When multiple users are involved, you want each user to acknowledge what network they are using. One of the main points of having multiple signing devices is to have the verification perform multiple times; IMO confirming the network is part of that. You could say the same thing about entering PINs and passwords. If you're doing single user multisig, you aren't doing it because it's easy, you're doing it for the security. It inherently isn't user friendly.


Anyways, if you feel strongly about having a testnet indicator in PSBT, bring it up on the mailing list.

And within a session you can both sign a PSBT and get new addresses, so you will need to have the network setting in order to support the latter.

I derive the network from the SLIP132 bytes of the extended public keys to avoid asking for the network setting when deriving addresses (although I recognize that SLIP132 is a not a bitcoin core standard):

buidl-python/multiwallet.py

Lines 182 to 185 in 4204afb

if xpub_prefix == "tpub":
is_testnet = True
elif xpub_prefix == "xpub":
is_testnet = False

One of the main points of having multiple signing devices is to have the verification perform multiple times; IMO confirming the network is part of that.

I don't understand this, can you give an example of when there is any utility in confirming the network with the end-user? I can't think of a circumstance where a user who confirms "I'm sending X BTC to address tb1... but also want to separately confirm the network I'm on." The address/network are definitionally linked.

if you feel strongly about having a testnet indicator in PSBT, bring it up on the mailing list.

Thanks!

Btw, @jimmysong @achow101 this is how I'm handling this from a UI perspective on https://github.com/mflaxman/multiwallet/

Screen Shot 2020-11-17 at 12 05 14 PM

I think it's pretty confusing from an end-user's perspective, but at least it works and doesn't require them to make a choice and they can just go with the default (provided they used "standard" bip32 paths).