Skip to content

Commit e1ae50b

Browse files
committed
Add: custom N, r and p for secret.hash
1 parent a8bbe13 commit e1ae50b

2 files changed

Lines changed: 51 additions & 24 deletions

File tree

bip38/bip38.py

Lines changed: 49 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
#!/usr/bin/env python3
22

3-
# Copyright © 2023-2024, Meheret Tesfaye Batu <meherett.batu@gmail.com>
3+
# Copyright © 2023-2025, Meheret Tesfaye Batu <meherett.batu@gmail.com>
44
# Distributed under the MIT software license, see the accompanying
55
# file COPYING or https://opensource.org/license/mit
66

@@ -27,7 +27,7 @@
2727
double_sha256, get_checksum
2828
)
2929
from .const import (
30-
N,
30+
NP,
3131
MAGIC_LOT_AND_SEQUENCE,
3232
MAGIC_NO_LOT_AND_SEQUENCE,
3333
NO_EC_MULTIPLIED_WIF_FLAG,
@@ -93,7 +93,8 @@ def intermediate_code(
9393
passphrase: str,
9494
lot: Optional[int] = None,
9595
sequence: Optional[int] = None,
96-
owner_salt: Optional[Union[str, bytes]] = None
96+
owner_salt: Optional[Union[str, bytes]] = None,
97+
N: int = 16384, r: int = 8, p: int = 8
9798
) -> str:
9899
"""
99100
Generates an intermediate passphrase.
@@ -104,8 +105,14 @@ def intermediate_code(
104105
:type lot: Optional[int]
105106
:param sequence: Optional sequence number (0-4095).
106107
:type sequence: Optional[int]
107-
:param owner_salt: Optional owner salt (default: random 8 bytes).
108+
:param owner_salt: Optional owner salt. (default: ``random 8 bytes``)
108109
:type owner_salt: Optional[str, bytes]
110+
:param N: CPU/memory cost parameter (must be a power of two > 1). Higher values increase security but require more resources. (default: ``16384``)
111+
:type N: int
112+
:param r: Block size parameter. Controls memory usage. Must be > 0. (default: ``8``)
113+
:type r: int
114+
:param p: Parallelization parameter. Defines how many threads can run in parallel. Must be > 0. (default: ``8``)
115+
:type p: int
109116
110117
:returns: The intermediate passphrase.
111118
:rtype: str
@@ -134,12 +141,12 @@ def intermediate_code(
134141
if not 0 <= sequence <= 4095:
135142
raise Error("Invalid sequence", expected="0 <= sequence <= 4095", got=sequence)
136143

137-
pre_factor: bytes = scrypt.hash(unicodedata.normalize("NFC", passphrase), owner_salt[:4], 16384, 8, 8, 32)
144+
pre_factor: bytes = scrypt.hash(unicodedata.normalize("NFC", passphrase), owner_salt[:4], N=N, r=r, p=p, buflen=32)
138145
owner_entropy: bytes = owner_salt[:4] + integer_to_bytes((lot * 4096 + sequence), 4)
139146
pass_factor: bytes = double_sha256(pre_factor + owner_entropy)
140147
magic: bytes = integer_to_bytes(MAGIC_LOT_AND_SEQUENCE)
141148
else:
142-
pass_factor: bytes = scrypt.hash(unicodedata.normalize("NFC", passphrase), owner_salt, 16384, 8, 8, 32)
149+
pass_factor: bytes = scrypt.hash(unicodedata.normalize("NFC", passphrase), owner_salt, N=N, r=r, p=p, buflen=32)
143150
magic: bytes = integer_to_bytes(MAGIC_NO_LOT_AND_SEQUENCE)
144151
owner_entropy: bytes = owner_salt
145152

@@ -150,7 +157,9 @@ def intermediate_code(
150157
magic + owner_entropy + pass_point.raw_compressed()
151158
))
152159

153-
def encrypt(self, wif: str, passphrase: str, network: Optional[str] = None) -> str:
160+
def encrypt(
161+
self, wif: str, passphrase: str, network: Optional[str] = None, N: int = 16384, r: int = 8, p: int = 8
162+
) -> str:
154163
"""
155164
Encrypts a Wallet Import Format (WIF) key using a passphrase with BIP38 encryption.
156165
@@ -160,6 +169,12 @@ def encrypt(self, wif: str, passphrase: str, network: Optional[str] = None) -> s
160169
:type passphrase: str
161170
:param network: Optional network for encryption. Defaults to the class's network if not provided.
162171
:type network: Optional[str]
172+
:param N: CPU/memory cost parameter (must be a power of two > 1). Higher values increase security but require more resources. (default: ``16384``)
173+
:type N: int
174+
:param r: Block size parameter. Controls memory usage. Must be > 0. (default: ``8``)
175+
:type r: int
176+
:param p: Parallelization parameter. Defines how many threads can run in parallel. Must be > 0. (default: ``8``)
177+
:type p: int
163178
164179
:returns: Encrypted WIF key as a string.
165180
:rtype: str
@@ -204,7 +219,7 @@ def encrypt(self, wif: str, passphrase: str, network: Optional[str] = None) -> s
204219
public_key_type=public_key_type
205220
)
206221
address_hash: bytes = get_checksum(get_bytes(address, unhexlify=False))
207-
key: bytes = scrypt.hash(unicodedata.normalize("NFC", passphrase), address_hash, 16384, 8, 8)
222+
key: bytes = scrypt.hash(unicodedata.normalize("NFC", passphrase), address_hash, N=N, r=r, p=p)
208223
derived_half_1, derived_half_2 = key[0:32], key[32:64]
209224

210225
aes: AESModeOfOperationECB = AESModeOfOperationECB(derived_half_2)
@@ -236,7 +251,7 @@ def create_new_encrypted_wif(
236251
237252
:param intermediate_passphrase: The intermediate passphrase.
238253
:type intermediate_passphrase: str
239-
:param wif_type: The WIF type, either 'wif' or 'wif-compressed' (default is 'wif').
254+
:param wif_type: The WIF type, either ``wif`` or ``wif-compressed``. (default is ``wif``)
240255
:type wif_type: str
241256
:param seed: Optional seed (default: random 24 bytes).
242257
:type seed: Optional[str, bytes]
@@ -298,7 +313,7 @@ def create_new_encrypted_wif(
298313
)
299314

300315
factor_b: bytes = double_sha256(seed_b)
301-
if not 0 < bytes_to_integer(factor_b) < N:
316+
if not 0 < bytes_to_integer(factor_b) < NP:
302317
raise Error("Invalid EC encrypted WIF (Wallet Import Format)")
303318

304319
public_key: PublicKey = PublicKey.from_point(
@@ -355,7 +370,7 @@ def create_new_encrypted_wif(
355370
)
356371

357372
def confirm_code(
358-
self, passphrase: str, confirmation_code: str, network: Optional[str] = None, detail: bool = False
373+
self, passphrase: str, confirmation_code: str, network: Optional[str] = None, detail: bool = False, N: int = 16384, r: int = 8, p: int = 8
359374
) -> Union[str, dict]:
360375
"""
361376
Confirms the passphrase using a confirmation code.
@@ -366,8 +381,14 @@ def confirm_code(
366381
:type confirmation_code: str
367382
:param network: Optional network for encryption. Defaults to the class's network if not provided.
368383
:type network: Optional[str]
369-
:param detail: Whether to return detailed info (default: False).
384+
:param detail: Whether to return detailed info. (default: ``False``)
370385
:type detail: bool
386+
:param N: CPU/memory cost parameter (must be a power of two > 1). Higher values increase security but require more resources. (default: ``16384``)
387+
:type N: int
388+
:param r: Block size parameter. Controls memory usage. Must be > 0. (default: ``8``)
389+
:type r: int
390+
:param p: Parallelization parameter. Defines how many threads can run in parallel. Must be > 0. (default: ``8``)
391+
:type p: int
371392
372393
:returns: Confirmation result as a string or detailed info as a dictionary.
373394
:rtype: Union[str, dict]
@@ -412,10 +433,10 @@ def confirm_code(
412433
else:
413434
owner_salt: bytes = owner_entropy
414435

415-
pass_factor: bytes = scrypt.hash(unicodedata.normalize("NFC", passphrase), owner_salt, 16384, 8, 8, 32)
436+
pass_factor: bytes = scrypt.hash(unicodedata.normalize("NFC", passphrase), owner_salt, N=N, r=r, p=p, buflen=32)
416437
if lot_and_sequence:
417438
pass_factor: bytes = double_sha256(pass_factor + owner_entropy)
418-
if bytes_to_integer(pass_factor) == 0 or bytes_to_integer(pass_factor) >= N:
439+
if bytes_to_integer(pass_factor) == 0 or bytes_to_integer(pass_factor) >= NP:
419440
raise Error("Invalid EC encrypted WIF (Wallet Import Format)")
420441

421442
pass_point: bytes = PrivateKey.from_bytes(pass_factor).public_key().raw_compressed()
@@ -470,7 +491,7 @@ def confirm_code(
470491
raise PassphraseError("Incorrect passphrase")
471492

472493
def decrypt(
473-
self, encrypted_wif: str, passphrase: str, network: Optional[str] = None, detail: bool = False
494+
self, encrypted_wif: str, passphrase: str, network: Optional[str] = None, detail: bool = False, N: int = 16384, r: int = 8, p: int = 8
474495
) -> Union[str, dict]:
475496
"""
476497
Decrypts an encrypted WIF (Wallet Import Format) using a passphrase.
@@ -481,8 +502,14 @@ def decrypt(
481502
:type passphrase: str
482503
:param network: Optional network for encryption. Defaults to the class's network if not provided.
483504
:type network: Optional[str]
484-
:param detail: Whether to return detailed info (default: False).
505+
:param detail: Whether to return detailed info. (default: ``False``)
485506
:type detail: bool
507+
:param N: CPU/memory cost parameter (must be a power of two > 1). Higher values increase security but require more resources. (default: ``16384``)
508+
:type N: int
509+
:param r: Block size parameter. Controls memory usage. Must be > 0. (default: ``8``)
510+
:type r: int
511+
:param p: Parallelization parameter. Defines how many threads can run in parallel. Must be > 0. (default: ``8``)
512+
:type p: int
486513
487514
:returns: The decrypted WIF or detailed private key info.
488515
:rtype: Union[str, dict]
@@ -530,7 +557,7 @@ def decrypt(
530557
)
531558

532559
key: bytes = scrypt.hash(
533-
unicodedata.normalize("NFC", passphrase), address_hash, 16384, 8, 8
560+
unicodedata.normalize("NFC", passphrase), address_hash, N=N, r=r, p=p
534561
)
535562
derived_half_1, derived_half_2 = key[0:32], key[32:64]
536563
encrypted_half_1: bytes = encrypted_wif_decode[7:23]
@@ -543,7 +570,7 @@ def decrypt(
543570
private_key: bytes = integer_to_bytes(
544571
bytes_to_integer(decrypted_half_1 + decrypted_half_2) ^ bytes_to_integer(derived_half_1)
545572
)
546-
if bytes_to_integer(private_key) == 0 or bytes_to_integer(private_key) >= N:
573+
if bytes_to_integer(private_key) == 0 or bytes_to_integer(private_key) >= NP:
547574
raise Error("Invalid Non-EC encrypted WIF (Wallet Import Format)")
548575

549576
public_key: PublicKey = PrivateKey.from_bytes(private_key).public_key()
@@ -584,10 +611,10 @@ def decrypt(
584611
else:
585612
owner_salt: bytes = owner_entropy
586613

587-
pass_factor: bytes = scrypt.hash(unicodedata.normalize("NFC", passphrase), owner_salt, 16384, 8, 8, 32)
614+
pass_factor: bytes = scrypt.hash(unicodedata.normalize("NFC", passphrase), owner_salt, N=N, r=r, p=p, buflen=32)
588615
if lot_and_sequence:
589616
pass_factor: bytes = double_sha256(pass_factor + owner_entropy)
590-
if bytes_to_integer(pass_factor) == 0 or bytes_to_integer(pass_factor) >= N:
617+
if bytes_to_integer(pass_factor) == 0 or bytes_to_integer(pass_factor) >= NP:
591618
raise Error("Invalid EC encrypted WIF (Wallet Import Format)")
592619

593620
pre_public_key: PublicKey = PrivateKey.from_bytes(pass_factor).public_key()
@@ -609,12 +636,12 @@ def decrypt(
609636
) + encrypted_half_1_half_2_seed_b_last_3[8:]
610637

611638
factor_b: bytes = double_sha256(seed_b)
612-
if bytes_to_integer(factor_b) == 0 or bytes_to_integer(factor_b) >= N:
639+
if bytes_to_integer(factor_b) == 0 or bytes_to_integer(factor_b) >= NP:
613640
raise Error("Invalid EC encrypted WIF (Wallet Import Format)")
614641

615642
# multiply private key
616643
private_key: bytes = integer_to_bytes(
617-
(bytes_to_integer(pass_factor) * bytes_to_integer(factor_b)) % N
644+
(bytes_to_integer(pass_factor) * bytes_to_integer(factor_b)) % NP
618645
)
619646
public_key: PublicKey = PrivateKey.from_bytes(private_key).public_key()
620647
wif_type: Literal["wif", "wif-compressed"] = "wif"

bip38/const.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
#!/usr/bin/env python3
22

3-
# Copyright © 2023-2024, Meheret Tesfaye Batu <meherett.batu@gmail.com>
3+
# Copyright © 2023-2025, Meheret Tesfaye Batu <meherett.batu@gmail.com>
44
# Distributed under the MIT software license, see the accompanying
55
# file COPYING or https://opensource.org/license/mit
66

@@ -25,7 +25,7 @@
2525
# Confirmation code prefix
2626
CONFIRMATION_CODE_PREFIX: int = 0x643bf6a89a
2727
# Number of points in the field
28-
N: int = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141
28+
NP: int = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141
2929
# Coordinate point length
3030
COORDINATE_POINT_LENGTH: int = 32
3131
# Private key length & prefixes

0 commit comments

Comments
 (0)