cantonbecker / bitcoinpaperwallet

JavaScript Client-Side Bitcoin Wallet Generator
https://www.bitaddress.org
453 stars 165 forks source link

Invalid BIP-38 for Compressed Pubkeys #32

Open praeluceo opened 6 years ago

praeluceo commented 6 years ago

When using the wallet to generate a BIP38 encrypted paper wall against a compressed pubkey, the decrypted key does not match the input.

For example, the compressed private key for "17bbQL696yc37SxbXwAs9Zi6yCwzBwhFH" is "L452QJZK6rn2imxzQrJiBArX25reWqwvdBuKzmv11YDQb7cgGHJf" which is compressed, when encrypted with "bitcoinpaperwallet" yields a BIP-38 wallet of "6PRJAp2oK4NXHe6dvPjazggtcSt6Sr737D79UA2K4isba4GGtfJWSrq3Hx".

However, decrypting the above wallet in bitcoinpaperwallet yields a public key of "1JpuWDAUoKHFVNzL3cmuhR2psac7JqfNfC" instead, with a private key of "5KKW9Ay1gF2hzjGsx8pQcZW8q24tvXXesrv5eMwKkwRWkN6eCSF".

There appears to be a bug with how bitcoinpaperwallet is encrypting compressed wallets.

cantonbecker commented 6 years ago

Hi there, Thanks for your report! Can you please provide step by step instructions on how you produced this error? E.g.

Step 1: Click on validate tab, and paste in this address and click the ‘validate/decrypt’ Step 2: Click the “use these details to print a paper wallet” button, etc.

On Dec 3, 2017, at 4:57 PM, praeluceo notifications@github.com wrote:

When using the wallet to generate a BIP38 encrypted paper wall against a compressed pubkey, the decrypted key does not match the input.

For example, the compressed private key for "17bbQL696yc37SxbXwAs9Zi6yCwzBwhFH" is "L452QJZK6rn2imxzQrJiBArX25reWqwvdBuKzmv11YDQb7cgGHJf" which is compressed, when encrypted with "bitcoinpaperwallet" yields a BIP-38 wallet of "6PRJAp2oK4NXHe6dvPjazggtcSt6Sr737D79UA2K4isba4GGtfJWSrq3Hx".

However, decrypting the above wallet in bitcoinpaperwallet yields a public key of "1JpuWDAUoKHFVNzL3cmuhR2psac7JqfNfC" instead, with a private key of "5KKW9Ay1gF2hzjGsx8pQcZW8q24tvXXesrv5eMwKkwRWkN6eCSF".

There appears to be a bug with how bitcoinpaperwallet is encrypting compressed wallets.

— You are receiving this because you are subscribed to this thread. Reply to this email directly, view it on GitHub https://github.com/cantonbecker/bitcoinpaperwallet/issues/32, or mute the thread https://github.com/notifications/unsubscribe-auth/AD9S40jmge62oCdvBiED6Nb1Lffoxec0ks5s8zVRgaJpZM4Qz9QT.


Canton Becker canton@gmail.com • (505) 570-0635 • http://cantonbecker.com

praeluceo commented 6 years ago

Apologies, of course. Steps to Reproduce:

  1. Go to https://iancoleman.io/bip39/ and generate a random 12-word seed
  2. Scroll down, ensuring you're at the BIP44 tab and select any private key you want to encrypt, note that these private keys are all presented as a compressed private key, and begin with either an L or K.
  3. Go to https://bitcoinpaperwallet.com/bitcoinpaperwallet/generate-wallet.html and skip the entropy generator, clicking straight through to the "Print Front" tab.
  4. Take the private key, click on the "enter my own key" button on the generator, and paste it into the text box.
  5. Hit "apply", and verify that the reported public key matches the private key you copied.
  6. Check the "BIP38 Encrypt" box, and enter any encryption passhrase you want, then click on "TURN ON BIP38 ENCRYPTION".
  7. Wait for your key to be encrypted, upon completion copy the BIP38 private key starting with "6P" from the wallet generator page, make note of your public key.
  8. Navigate to the "Validate or Decrypt" tab, paste in your BIP38 encrypted private key, type in your passphrase, click on "Decrypt BIP38".
  9. Note that the displayed public key no-longer matches the desired public key, and that the private key does not match the private key from step 2.

I haven't done a lot of digging yet, but it appears to be an issue where the BIP38 encryption is expecting an uncompressed pubkey/privkey combination (starting with a 5). My next step was going to be to uncompress the same private key used to reproduce the failure, but I haven't looked heavily into how to uncompress them yet.

praeluceo commented 6 years ago

Got a little closer, I just went to https://www.bitaddress.org/ (where this was forked from) and the defect exists there too. I can generate a bip38 compressed private key on bitaddress.org, and switch over to the verify tab, and it gives me a different public key than it originally displayed. Tested that "6P" address on here, and got the identical wrong address.

I then went to bitaddress and generated an uncompressed BIP38 encrypted private key, and it -did- decrypt to the original public key. And it did on bitcoinpaperwallet as well.

cantonbecker commented 6 years ago

Ah, I see what you mean now.

I believe everything works properly, no cause for alarm:

Note that when you use the ‘validate’ tab to decrypt the wallet, by default it ONLY shows you the non-compressed public & private keys. Look on the page for a little orange link that says, "Display compressed format keys?”

Click this and you’ll see the compressed keys you were looking for.

By default, I don’t show users compressed keys on bitcoinpaperwallet.com because it was causing many of them a lot of confusion and scary moments.

Let me know if this resolves your concern. (And thanks for bringing it up!)

praeluceo commented 6 years ago

OK, so that worked! But now I'm a little confused. So revealing the compressed keys displays the original public key as expected, but the behavior still doesn't match. For example, when scanning the private key to sweep into Mycelium or Samourai, the displayed public key is the Public uncompressed address. But the public key printed on the wallet itself, is the public compressed address. And if the public compressed address is funded by scanning the paper wallet, and if the private key is swept by a mobile wallet, then the balance on the recipient's phone is empty. For instance:

  1. Generate a new compressed keypair: 1JTgVXzMCgnh2S5rowvAcuZRDj65eTxYtZ & L2hv26igH5nSN9bVd4mjKQm3yRc54dYLu3YyTESMTtF6nsM2kPfs
  2. Go to Bitcoinpaperwallet and input "L2hv26igH5nSN9bVd4mjKQm3yRc54dYLu3YyTESMTtF6nsM2kPfs", press "apply", confirm that it represents the private key to "1JT...tZ"
  3. Check BIP38 encrypt with the passphrase of "test"
  4. Verify that the public key has remained "1JT...tZ" and the new private key is "6PRPVo5DSs1bGMFmBxpb1AxstEShPGyUq27sjAr8FubXMWWFrDzsVDLGt1"
  5. Hypothetically, load 1 BTC onto "1JT...tZ"
  6. Go to "Validate or Decrypt" tab, enter BIP38 private key & passphrase, verify that the displayed Public Key is now, "1DMjz5d324YFdpEeKhMqRWWr8uiru8KWGN" instead of "1JT...tZ" with a private key of "5K4NytbN8isApqcuhoAgVe6uL8PSqrWkvw3iLY91ZNaZ8EFURic".
  7. Click on the "Display compressed format keys" button and verify that the correct public/private keypair is shown as expected (no data loss, hooray!)
  8. However, now Using Mycelium scan the paper wallet to add a new private key. Enter the passphrase, select BTC, save the wallet, and note that the displayed public key is "1DMjz5...u8KWGN" and is unfunded. The 1 BTC balance remains on the compressed key.
  9. Open Samourai and confirm that the sweep behaves the same.
  10. Go back to bitcoinpaperwallet, and scan the newly revealed private key from the "display compressed format keys" entry, and tada, you have your "1JTgVX...eTxYtZ" public key!

In the end, what I believe, is that we are seeing a mix of compressed and uncompressed keys used inconsistently on the wallet generator. If "1JTgVXzMCgnh2S5rowvAcuZRDj65eTxYtZ" is shown as the public key, then "L2hv26igH5nSN9bVd4mjKQm3yRc54dYLu3YyTESMTtF6nsM2kPfs" should be used for the BIP38 encrypted but if the private key decrypts to "5K4NytbN8isApqcuhoAgVe6uL8PSqrWkvw3iLY91ZNaZ8EFURic" then the public key -has- to reflect that as being "1DMjz5d324YFdpEeKhMqRWWr8uiru8KWGN".

So recovery is possible, but is not straight-forward from either wallets I tested with. Additionally, the confusion with compressed keys (which was confusing even for me!) is that you end up with new public keys from the uncompressed BIP38 decrypted wallet. I was using an HD-wallet to generate dozens of keypairs, entering them into bitcoinpaperwallet, and funding them in bulk. So when I did a test sweep and it failed, it was unsettling because the public/private keypair it did generate, do not appear in my Derivation Path from my BIP44 wallet.

cantonbecker commented 6 years ago

Hello, Thanks for the very clear follow-up.

What I’m not clear on is why this scenario would happen in typical circumstances:

Generate a new compressed keypair: 1JTgVXzMCgnh2S5rowvAcuZRDj65eTxYtZ & L2hv26igH5nSN9bVd4mjKQm3yRc54dYLu3YyTESMTtF6nsM2kPfs Go to Bitcoinpaperwallet and input "L2hv26igH5nSN9bVd4mjKQm3yRc54dYLu3YyTESMTtF6nsM2kPfs", press "apply", confirm that it represents the private key to "1JT...tZ" Check BIP38 encrypt with the passphrase of "test" Verify that the public key has remained "1JT...tZ" and the new private key is "6PRPVo5DSs1bGMFmBxpb1AxstEShPGyUq27sjAr8FubXMWWFrDzsVDLGt1" Hypothetically, load 1 BTC onto "1JT…tZ"

In your scenario, I just printed out a paper wallet that shows this private key:

• 6PRPVo5DSs1bGMFmBxpb1AxstEShPGyUq27sjAr8FubXMWWFrDzsVDLGt1

Why would I then load 1 BTC onto 1JTgVXzMCgnh2S5rowvAcuZRDj65eTxYtZ? Presumably, if I’m making a paper wallet, I’m going to load the funds onto the address that is shown on the paper wallet itself.

If you need to generate your private keys using a different tool, I recommend you generate compressed key pairs since that’s what bitcoinpaperwallet.com defaults to.

I do wish that compressed/uncompressed addresses didn’t basically act like different addresses, but that’s how it is. My solution (which is a bit different from bitaddress.org etc.) has been just to avoid revealing compressed addresses altogether. When bitcoinpaperwallet.com made both versions more plain, people got themselves into trouble and panicked.

That said, again, thanks for your report, but I’m reluctant to do adjust how bitcoinpaperwallet.com works for what seems to me to be very much an edge case, and one that doesn’t actually cause a loss of funds — just a moment of panic :)


Canton Becker canton@gmail.com • (505) 570-0635 • http://cantonbecker.com

praeluceo commented 6 years ago

When you take those steps, on the paper wallet you just printed, does it show a private key of 6PRPVo5DSs1bGMFmBxpb1AxstEShPGyUq27sjAr8FubXMWWFrDzsVDLGt1 and if so, does it show the QR code for the public key of "1JTgVXzMCgnh2S5rowvAcuZRDj65eTxYtZ" on the outside of the wallet, or does it show the public key of "1DMjz5d324YFdpEeKhMqRWWr8uiru8KWGN"?

Because in my scenario, I see a QR code for the public key of 1TJ... and a QR code for the private key of 6PRP..., so if I funded the wallet by scanning the outer QR Code, and if I attempted to sweep the wallet with Mycelium or Samourai, I would not sweep the balance displayed. I'll retest when I get home to make sure I'm seeing what I think I'm seeing, but just by using the website, that's how it appears to me.

praeluceo commented 6 years ago

So I guess this bug is a duplicate of https://github.com/pointbiz/bitaddress.org/issues/114 really, I've worked a temporary solution where I modified a single line to display uncompressed keys:

-                       var flagByte = compressed ? 0xe0 : 0xc0;
+                       var flagByte = uncompressed ? 0xe0 : 0xc0;

This gets me closer to what I need where when the private key is scanned by a wallet, the public key that shows up on the software wallet is the same one as shows on the physical paper wallet. I know there are issues with BIP32 and uncompressed wallets, but I'm not sure what those are. My next step is to get the ability to generate uncompressed public keys, and feed pre-encrypted private/public key pairs into the generator (so I don't have to have access to the private key).

xioustic commented 5 years ago

This is still an issue today, which sucks because I think this is the best paper wallet generator for Bitcoin. I suppose paper wallets have become deprecated and an obscurity these days.

I have fixed it with a few lines of code but according to the README the author is not accepting pull requests? If so, are there any actively maintained forks? Or trusted alternatives?

I'm away from the computer for a week or two, but the fix is essentially (apologies for any mistakes):

  1. Modify "GenerateNewWallet" function on 11956
  2. Insert "var compressed = (new Bitcoin.ECKey(addressSeed.wifKey)).compressed" at line 11963 after the check if the option for encryption was enabled.
  3. Make sure "compressed" is passed into the third argument instead of "false" in the call on 11965 BIP38PrivateKeyToEncryptedKeyAsync.

I have not thoroughly tested this fix but it appears to have worked using a few manual test cases when generating wallets.

Per the fix, the mistake was simply not indicating whether the key was compressed to the BIP38 encrypting function and instead just passing in "false", causing the returned BIP38 to always wrap the uncompressed version of the private key.

This is a problem, as per standard I've noticed among wallets:

For reference, the current version of bitaddress.org (project that bitcoinpaperwallet was forked from) does not have this bug as far as I can tell. Perhaps it did in the past though.

As this issue is old, I will provide a verbose summary in addition to the fix above with hopes it will help others find this solution:

I've gone to recover one of my paper wallets in the past and when unlocked in a popular wallet like Mycelium it yields a completely different address (the uncompressed one). This is because BitcoinPaperWallet converts whatever private key it is given into the uncompressed one before wrapping it in BIP38. The unlocking wallet then naturally assumes the user wants the uncompressed key corresponding to the uncompressed address.

Worse yet, the user's expected public key (the compressed address) is displayed on the generated wallet so there is no indication that the conversion to the uncompressed private key occured until you go to unlock the wallet to retrieve the funds. To someone who does not understand the intricacies of Bitcoin address/key formats, they'd think the Bitcoin is then irretrievable since the paper wallet unlocks a completely different address.

The funds can be recovered by unlocking the BIP38 on bitaddress.org or in the validate feature of bitcoinpaperwallet and also clicking the "show compressed" link on that tab. The correct raw compressed private key will be next to the correct compressed address, which can then be scanned by a standard wallet.

This issue can only happen on addresses you import into bitcoinpaperwallet yourself that you generated elsewhere. Left to its own devices, I think bitcoinpaperwallet will only ever generate uncompressed private keys which skirts the issue entirely. This makes the bug relatively obscure (only occuring for a specific use case) but no less catastrophic to the unfortunate and misinformed.

linuxman21 commented 4 years ago

Unfortunately cantonbecker has sold his paperwallet projects to a new owner a little over a year ago and they have not done any updates since taking ownership.