Bitcoin Forum
May 04, 2024, 02:51:32 AM *
News: Latest Bitcoin Core release: 27.0 [Torrent]
 
   Home   Help Search Login Register More  
Pages: [1]
  Print  
Author Topic: [SOLVED] Help with making BIP38 class standard-compliant  (Read 76 times)
NotATether (OP)
Legendary
*
Offline Offline

Activity: 1596
Merit: 6727


bitcoincleanup.com / bitmixlist.org


View Profile WWW
June 05, 2023, 10:21:39 AM
Last edit: June 05, 2023, 10:42:00 AM by NotATether
Merited by hugeblack (4)
 #1

A minor error: I forgot to switch off the compressed mode in the BIP38 encryption. Doing that resolved the test case. I'm still mildly frustrated that none of the test vectors have bytes values in them, as the Base58 can make slightly incorrect results look wildly different. Leaving this thread open in case someone has something to say about this.

I have a BIP38 class which encrypts and decrypts private keys (see https://github.com/ZenulAbidin/zpywallet for details), but it's creating the wrong encrypted keys.

I am 100% sure the AES encryption/decryption works properly.

Code (/zpywallet/utils/bip38.py):

Code:
import hashlib
import binascii
import base58
import scrypt

from .utils.keys import PrivateKey
from .utils.utils import encrypt, decrypt

class Bip38PrivateKey:
    BLOCK_SIZE = 16
    KEY_LEN = 32
    IV_LEN = 16

    def __init__(self, privkey: PrivateKey, passphrase, compressed=True, segwit=False, witness_version=0):
        '''BIP0038 non-ec-multiply encryption. Returns BIP0038 encrypted privkey.'''
        if "BASE58" not in privkey.network.ADDRESS_MODE and "BECH32" not in privkey.network.ADDRESS_MODE:
            raise ValueError("BIP38 requires Base58 or Bech32 addresses")
        flagbyte = b'\xe0' if compressed else b'\xc0'
        addr = privkey.public_key.bech32_address(compressed, witness_version) if segwit else privkey.public_key.base58_address(compressed)
        addresshash = hashlib.sha256(hashlib.sha256(addr).digest()).digest()[0:4]
        key = scrypt.hash(passphrase, addresshash, 16384, 8, 8)
        derivedhalf1 = key[0:32]
        derivedhalf2 = key[32:64]
        encryptedhalf1 = encrypt(binascii.unhexlify('%0.32x' % (int(binascii.hexlify(bytes(privkey)[0:16]), 16) ^ int(binascii.hexlify(derivedhalf1[0:16]), 16))), derivedhalf2)
        encryptedhalf2 = encrypt(binascii.unhexlify('%0.32x' % (int(binascii.hexlify(bytes(privkey)[16:32]), 16) ^ int(binascii.hexlify(derivedhalf1[16:32]), 16))), derivedhalf2)
        self.flagbyte = flagbyte
        self.addresshash = addresshash
        self.encryptedhalf1 = encryptedhalf1
        self.encryptedhalf2 = encryptedhalf2
        encrypted_privkey = b'\x01\x42' + self.flagbyte + self.addresshash + self.encryptedhalf1 + self.encryptedhalf2
        encrypted_privkey += hashlib.sha256(hashlib.sha256(encrypted_privkey).digest()).digest()[:4] # b58check for encrypted privkey
        self._encrypted_privkey = base58.b58encode(encrypted_privkey)

    @property
    def base58(self):
        return self._encrypted_privkey.decode()
        

    def private_key(self, passphrase, compressed=True, segwit=False, witness_version=0):
        '''BIP0038 non-ec-multiply decryption. Returns WIF privkey.'''
        d = base58.b58decode(self._encrypted_privkey)
        d = d[2:]
        #flagbyte = d[0:1]
        d = d[1:]
        #WIF compression
        #if flagbyte == b'\xc0':
        #    compressed = False
        #if flagbyte == b'\xe0':
        #    compressed = True
        addresshash = d[0:4]
        d = d[4:-4]
        key = scrypt.hash(passphrase,addresshash, 16384, 8, 8)
        derivedhalf1 = key[0:32]
        derivedhalf2 = key[32:64]
        encryptedhalf1 = d[0:16]
        encryptedhalf2 = d[16:32]
        decryptedhalf2 = decrypt(encryptedhalf2, derivedhalf2)
        decryptedhalf1 = decrypt(encryptedhalf1, derivedhalf2)
        priv = decryptedhalf1 + decryptedhalf2
        priv = PrivateKey.from_bytes(binascii.unhexlify('%064x' % (int(binascii.hexlify(priv), 16) ^ int(binascii.hexlify(derivedhalf1), 16))))
        pub = priv.public_key

        addr = pub.bech32_address(compressed, witness_version) if segwit else pub.base58_address(compressed)
        if hashlib.sha256(hashlib.sha256(addr).digest()).digest()[0:4] != addresshash:
            raise ValueError('Verification failed. Password is incorrect.')
        else:
            return priv

Input parameters from BIP-0038 test vector:

Code:
No compression, no EC multiply
Test 1:

Passphrase: TestingOneTwoThree
Encrypted: 6PRVWUbkzzsbcVac2qwfssoUJAN1Xhrg6bNk8J7Nzm5H7kxEbn2Nh2ZoGg
Unencrypted (WIF): 5KN7MzqK5wt2TP1fQCYyHBtDrXdJuXbUzm4A9rKAteGu3Qi5CVR
Unencrypted (hex): CBF4B9F70470856BB4F40F80B87EDB90865997FFEE6DF315AB166D713AF433A5

only the WIF is used as input.

Debug output from PDB:

Code:
(Pdb) p flagbyte
b'\xe0'
(Pdb) p addr
b'164MQi977u9GUteHr4EPH27VkkdxmfCvGW'
(Pdb) p addresshash
b'C\xbeAy'
(Pdb) p key
b's\x1e\xf3\xc77\xb5]\xf4\x99\x8bD\xfa\x8aTz?8\xdfBM\xa2@\xde8\x9b\x11\xd1\x87[\xa4wg//\xe8\x1b\x052\xb5\x95\x0e>\xa6\xff\xf9,e\xd4g\xaa}\x05Ii\x82\x1d\xe24Oz\x86\xd4%i'
(Pdb) p derivedhalf1
b's\x1e\xf3\xc77\xb5]\xf4\x99\x8bD\xfa\x8aTz?8\xdfBM\xa2@\xde8\x9b\x11\xd1\x87[\xa4wg'
(Pdb) p derivedhalf2
b'//\xe8\x1b\x052\xb5\x95\x0e>\xa6\xff\xf9,e\xd4g\xaa}\x05Ii\x82\x1d\xe24Oz\x86\xd4%i'
(Pdb) encryptedhalf1
b'p\xe4\xa0\x80_\x15\xa7~\xfcs\x8fy@h\xd8\x83'
(Pdb) encryptedhalf2
b'|)\x85\xa6\x94_\x7f\xe0\xdb?u\xdc0^\xaf|'
(Pdb) encrypted_privkey
b'\x01B\xe0C\xbeAyp\xe4\xa0\x80_\x15\xa7~\xfcs\x8fy@h\xd8\x83|)\x85\xa6\x94_\x7f\xe0\xdb?u\xdc0^\xaf|\xbba\xce$'
(Pdb)  hashlib.sha256(hashlib.sha256(encrypted_privkey).digest()).digest()[:4]
b'8J\xa7\x12'
(Pdb) self._encrypted_privkey
b'6PYNKZ1EAgYgmQfmNVamxyXVWHzK5s6DGhwP4J5o44cvXdoY7sRzhtpUeo'

As you can see, instead of 6PRVWUbkzzsbcVac2qwfssoUJAN1Xhrg6bNk8J7Nzm5H7kxEbn2Nh2ZoGg, I am getting 6PYNKZ1EAgYgmQfmNVamxyXVWHzK5s6DGhwP4J5o44cvXdoY7sRzhtpUeo which is wrong. So what is wrong with the BIP38 encryption?

.
.BLACKJACK ♠ FUN.
█████████
██████████████
████████████
█████████████████
████████████████▄▄
░█████████████▀░▀▀
██████████████████
░██████████████
████████████████
░██████████████
████████████
███████████████░██
██████████
CRYPTO CASINO &
SPORTS BETTING
▄▄███████▄▄
▄███████████████▄
███████████████████
█████████████████████
███████████████████████
█████████████████████████
█████████████████████████
█████████████████████████
███████████████████████
█████████████████████
███████████████████
▀███████████████▀
█████████
.
1714791092
Hero Member
*
Offline Offline

Posts: 1714791092

View Profile Personal Message (Offline)

Ignore
1714791092
Reply with quote  #2

1714791092
Report to moderator
1714791092
Hero Member
*
Offline Offline

Posts: 1714791092

View Profile Personal Message (Offline)

Ignore
1714791092
Reply with quote  #2

1714791092
Report to moderator
The forum strives to allow free discussion of any ideas. All policies are built around this principle. This doesn't mean you can post garbage, though: posts should actually contain ideas, and these ideas should be argued reasonably.
Advertised sites are not endorsed by the Bitcoin Forum. They may be unsafe, untrustworthy, or illegal in your jurisdiction.
witcher_sense
Legendary
*
Offline Offline

Activity: 2338
Merit: 4316

🔐BitcoinMessage.Tools🔑


View Profile WWW
June 05, 2023, 11:44:14 AM
 #2

A minor error: I forgot to switch off the compressed mode in the BIP38 encryption. Doing that resolved the test case. I'm still mildly frustrated that none of the test vectors have bytes values in them, as the Base58 can make slightly incorrect results look wildly different. Leaving this thread open in case someone has something to say about this.
Good to know you solved your problem. Could you also explain how to run this software or this particular module to test it? What is the entry point for this wallet? I tried to run the wallet.py file and a couple of others, but always get an "ImportError: attempted relative import with no known parent package."

█▀▀▀











█▄▄▄
▀▀▀▀▀▀▀▀▀▀▀
e
▄▄▄▄▄▄▄▄▄▄▄
█████████████
████████████▄███
██▐███████▄█████▀
█████████▄████▀
███▐████▄███▀
████▐██████▀
█████▀█████
███████████▄
████████████▄
██▄█████▀█████▄
▄█████████▀█████▀
███████████▀██▀
████▀█████████
▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
c.h.
▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
▀▀▀█











▄▄▄█
▄██████▄▄▄
█████████████▄▄
███████████████
███████████████
███████████████
███████████████
███░░█████████
███▌▐█████████
█████████████
███████████▀
██████████▀
████████▀
▀██▀▀
NotATether (OP)
Legendary
*
Offline Offline

Activity: 1596
Merit: 6727


bitcoincleanup.com / bitmixlist.org


View Profile WWW
June 05, 2023, 01:44:27 PM
 #3

A minor error: I forgot to switch off the compressed mode in the BIP38 encryption. Doing that resolved the test case. I'm still mildly frustrated that none of the test vectors have bytes values in them, as the Base58 can make slightly incorrect results look wildly different. Leaving this thread open in case someone has something to say about this.
Good to know you solved your problem. Could you also explain how to run this software or this particular module to test it? What is the entry point for this wallet? I tried to run the wallet.py file and a couple of others, but always get an "ImportError: attempted relative import with no known parent package."

Don't use git clone because the latest commits broke a few things, instead you should install the latest version from PyPI (v0.4.0 at the time of writing):

Code:
pip install zpywallet

Documentation is also being written for the next release which will hopefully make things more clear.

.
.BLACKJACK ♠ FUN.
█████████
██████████████
████████████
█████████████████
████████████████▄▄
░█████████████▀░▀▀
██████████████████
░██████████████
████████████████
░██████████████
████████████
███████████████░██
██████████
CRYPTO CASINO &
SPORTS BETTING
▄▄███████▄▄
▄███████████████▄
███████████████████
█████████████████████
███████████████████████
█████████████████████████
█████████████████████████
█████████████████████████
███████████████████████
█████████████████████
███████████████████
▀███████████████▀
█████████
.
Pages: [1]
  Print  
 
Jump to:  

Powered by MySQL Powered by PHP Powered by SMF 1.1.19 | SMF © 2006-2009, Simple Machines Valid XHTML 1.0! Valid CSS!