It must be the same result. If it is used:
prefactor = hexstrlify(scrypt.hash(password, salt, 16384, 8, 8, 32))
Maybe there are different tools that have different prefixes. (nothing else can cause confusion)
Range in base58check encoding for non-EC-multiplied keys without compression (prefix 6PR):
Minimum value: 6PRHv1jg1ytiE4kT2QtrUz8gEjMQghZDWg1FuxjdYDzjUkcJeGdFj9q9Vi (based on 01 42 C0 plus thirty-six 00's)
Maximum value: 6PRWdmoT1ZursVcr5NiD14p5bHrKVGPG7yeEoEeRb8FVaqYSHnZTLEbYsU (based on 01 42 C0 plus thirty-six FF's)
Range in base58check encoding for non-EC-multiplied keys with compression (prefix 6PY):
Minimum value: 6PYJxKpVnkXUsnZAfD2B5ZsZafJYNp4ezQQeCjs39494qUUXLnXijLx6LG (based on 01 42 E0 plus thirty-six 00's)
Maximum value: 6PYXg5tGnLYdXDRZiAqXbeYxwDoTBNthbi3d61mqBxPpwZQezJTvQHsCnk (based on 01 42 E0 plus thirty-six FF's)
Range in base58check encoding for EC-multiplied keys without compression (prefix 6Pf):
Minimum value: 6PfKzduKZXAFXWMtJ19Vg9cSvbFg4va6U8p2VWzSjtHQCCLk3JSBpUvfpf (based on 01 43 00 plus thirty-six 00's)
Maximum value: 6PfYiPy6Z7BQAwEHLxxrCEHrH9kasVQ95ST1NnuEnnYAJHGsgpNPQ9dTHc (based on 01 43 00 plus thirty-six FF's)
Range in base58check encoding for EC-multiplied keys with compression (prefix 6Pn):
Minimum value: 6PnM2wz9LHo2BEAbvoGpGjMLGXCom35XwsDQnJ7rLiRjYvCxjpLenmoBsR (based on 01 43 20 plus thirty-six 00's)
Maximum value: 6PnZki3vKspApf2zym6Anp2jd5hiZbuaZArPfa2ePcgVf196PLGrQNyVUh (based on 01 43 20 plus thirty-six FF's)
I have my own tool that only encode/decode EC-multiplied keys (prefix 6Pn).
import scrypt
from binascii import hexlify, unhexlify
from Crypto.Cipher import AES # pip install pycryptodome
from gmpy2 import mpz # pip install gmpy2
import secp256k1 as ice # https://github.com/iceland2k14/secp256k1
import hashlib
# Utility function to decode base58
def b58d(s, check=True):
b58_digits = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"
zero_digit = b58_digits[0]
assert s
n = 0
for c in s:
n = n * 58 + b58_digits.index(c)
h = hex(n)[2:]
if len(h) % 2:
h = "0" + h
res = unhexlify(h.encode("utf8"))
pad = 0
for c in s:
if c == zero_digit:
pad += 1
else:
break
o = b"\x00" * pad + res
if check:
double_sha256 = hashlib.sha256(hashlib.sha256(o[:-4]).digest()).digest()
assert double_sha256[:4] == o[-4:]
return hexlify(o[:-4]).decode("ascii")
else:
return hexlify(o).decode("ascii")
def simple_aes_decrypt(msg, key):
assert len(msg) == 16
assert len(key) == 32
cipher = AES.new(key, AES.MODE_ECB)
decrypted_msg = hexstrlify(cipher.decrypt(msg))
decrypted_msg = decrypted_msg.rstrip('7b')
padding_length = (32 - len(decrypted_msg)) // 2
decrypted_msg += '7b' * padding_length
assert len(decrypted_msg) == 32
return unhexlify(decrypted_msg)
def dechex(num, zfill=0):
if isinstance(num, mpz):
hex_num = num.digits(16)
if len(hex_num) % 2:
hex_num = '0' + hex_num
hex_bytes = hex_num.rjust(zfill * 2, '0')
return hex_bytes
else:
raise TypeError('Input must be mpz.')
def multiplypriv(p1, p2):
result = (mpz(p1, 16) * mpz(p2, 16)) % ice.N
return dechex(result, 32)
def strlify(a):
return str(a).replace("b'", "").rstrip("'")
def hexstrlify(a):
return strlify(hexlify(a))
def privtopub(priv, outcompressed=True):
priv_int = int(priv, 16)
pub = ice.scalar_multiplication(priv_int)
return ice.point_to_cpub(pub)
def hash256(hexstring):
return hashlib.sha256(hashlib.sha256(bytes.fromhex(hexstring)).digest()).digest().hex()
def bip38decrypt(password, encrypted_private_key, target):
encrypted_private_key = b58d(encrypted_private_key)
owner_entropy = encrypted_private_key[14:30]
enchalf1half1 = encrypted_private_key[30:46]
enchalf2 = encrypted_private_key[46:]
owner_salt = owner_entropy
salt = unhexlify(owner_salt)
prefactor = hexstrlify(scrypt.hash(password, salt, 16384, 8, 8, 32))
passfactor = prefactor
passpoint = privtopub(passfactor, True)
password = unhexlify(passpoint)
combined_salt = unhexlify(encrypted_private_key[6:14] + owner_entropy)
encseedb = hexstrlify(scrypt.hash(password, combined_salt, 1024, 1, 1, 64))
key = unhexlify(encseedb[64:])
tmp = hexstrlify(simple_aes_decrypt(unhexlify(enchalf2), key))
enchalf1half2_seedblastthird = mpz(tmp, 16) ^ mpz(encseedb[32:64], 16)
enchalf1half2_seedblastthird = dechex(enchalf1half2_seedblastthird, 16)
enchalf1half2 = enchalf1half2_seedblastthird[:16]
enchalf1 = enchalf1half1 + enchalf1half2
decrypted_enchalf1 = hexstrlify(simple_aes_decrypt(unhexlify(enchalf1), key))
seedb = mpz(decrypted_enchalf1, 16) ^ mpz(encseedb[:32], 16)
seedb = dechex(seedb, 16) + enchalf1half2_seedblastthird[16:]
factorb = hash256(seedb)
priv = multiplypriv(passfactor, factorb)
dec = int(priv, 16)
caddr = ice.privatekey_to_address(0, True, dec)
uaddr = ice.privatekey_to_address(0, False, dec)
if caddr == target or uaddr == target:
wifc = ice.btc_pvk_to_wif(priv)
wifu = ice.btc_pvk_to_wif(priv, False)
with open("KEY.txt", "a") as file:
file.write("\nPrivate key (wif) Compressed : " + wifc)
file.write("\nPrivate key (wif) Uncompressed: " + wifu)
file.write("\nBitcoin address Compressed: " + caddr)
file.write("\nBitcoin address Uncompressed: " + uaddr)
file.write(
"\n-------------------------------------------------------------------------------------------------------------------------------------------\n"
)
return wifc
else:
return False
pwd = "Satoshi"
encryptedSecret = "6PnRY7S41Qe6i9SLxRrmSJ1AQhkz4yLjPXw76qtHShLsb1Ch8JrbMWGvPr"
target = "15aAb6P6ysSAR3SEtit6MWWgNPXZgn5YFj"
test = bip38decrypt(pwd, encryptedSecret, target)
print(test)
result form KEY.txt
Private key (wif) Compressed : KxSomWg95w2qRi5S3cuC5FPcQdXiWhHRaWpZZcLkXgvE1UAyhfZq
Private key (wif) Uncompressed: 5J6Pq9Y56ecm3szePoCNYKfevqc44ZEh1Lu1afpXFf3YVh13Ccb
Bitcoin address Compressed: 15aAb6P6ysSAR3SEtit6MWWgNPXZgn5YFj
Bitcoin address Uncompressed: 13XDLESCf3UDBLjGoSEdLH9ksHNuYAybPR
bip38 is pretty well DEPRECATED by now and not really recommended.
The tools, for example, in python, are outdated.
But that doesn't stop me from updating them myself. BIP38 is unhackable.
I barely manage to encode/decode 100 per second with all possible accelerations around.
The scrypt function is slow by design.
It can NOT be accelerated. The parameters that include N=16384, r=8, and p=8.
The N parameter defines the CPU/memory cost, and larger values like that make the function more memory-intensive.
This is a deliberate design choice to prevent attackers from using specialized hardware, like GPUs or ASICs, which might have less memory available per processing unit.