From 21d515ab39c72b6bf2d936f073eb45159172b12b Mon Sep 17 00:00:00 2001 From: Silberengel Date: Sun, 19 Apr 2026 01:17:31 +0200 Subject: [PATCH] bug-fix --- src/imwald/core/nip19.py | 9 ++++++++- src/imwald/core/nip49.py | 27 +++++++++++++++++++++------ 2 files changed, 29 insertions(+), 7 deletions(-) diff --git a/src/imwald/core/nip19.py b/src/imwald/core/nip19.py index 58d4a3a..d0aa3bc 100644 --- a/src/imwald/core/nip19.py +++ b/src/imwald/core/nip19.py @@ -9,13 +9,18 @@ def decode_nsec(nsec: str) -> bytes: hrp, data = bech32.bech32_decode(nsec.strip()) if hrp != "nsec" or data is None: raise ValueError("invalid nsec") - return bytes(bech32.convertbits(data, 5, 8, False)) + conv = bech32.convertbits(data, 5, 8, False) + if conv is None: + raise ValueError("invalid nsec payload") + return bytes(conv) def encode_nsec(secret: bytes) -> str: if len(secret) != 32: raise ValueError("secret must be 32 bytes") conv = bech32.convertbits(list(secret), 8, 5, True) + if conv is None: + raise ValueError("invalid secret encoding") return bech32.bech32_encode("nsec", conv) @@ -24,4 +29,6 @@ def encode_npub(pubkey_hex: str) -> str: if len(raw) != 32: raise ValueError("pubkey must be 32 bytes hex") conv = bech32.convertbits(list(raw), 8, 5, True) + if conv is None: + raise ValueError("invalid pubkey encoding") return bech32.bech32_encode("npub", conv) diff --git a/src/imwald/core/nip49.py b/src/imwald/core/nip49.py index 9b10ac5..c97e9e5 100644 --- a/src/imwald/core/nip49.py +++ b/src/imwald/core/nip49.py @@ -17,6 +17,17 @@ _SCRYPT_R: Final = 8 _SCRYPT_P: Final = 1 +def _scrypt_maxmem_bytes(n: int, r: int) -> int: + """Upper bound for ``hashlib.scrypt(..., maxmem=…)`` OpenSSL will accept. + + RFC 7914 block mixing uses about ``128 * N * r`` bytes; some OpenSSL 3.x + builds still raise *memory limit exceeded* when ``maxmem`` is only that + minimum, so we add a fixed and proportional allowance. + """ + romix = 128 * n * r + return romix + max(64 * 1024 * 1024, romix // 8) + + def _normalize_password(password: str) -> bytes: return unicodedata.normalize("NFKC", password).encode("utf-8") @@ -25,12 +36,17 @@ def _decode_ncryptsec(ncryptsec: str) -> bytes: hrp, data = bech32.bech32_decode(ncryptsec) if hrp != "ncryptsec" or data is None: raise ValueError("invalid ncryptsec hrp or data") - return bytes(bech32.convertbits(data, 5, 8, False)) + conv = bech32.convertbits(data, 5, 8, False) + if conv is None: + raise ValueError("invalid ncryptsec payload") + return bytes(conv) def _encode_ncryptsec(payload: bytes) -> str: - data = bech32.convertbits(list(payload), 8, 5, True) - return bech32.bech32_encode("ncryptsec", data) + words = bech32.convertbits(list(payload), 8, 5, True) + if words is None: + raise ValueError("invalid ncryptsec encoding") + return bech32.bech32_encode("ncryptsec", words) def decrypt_ncryptsec(ncryptsec: str, password: str) -> bytes: @@ -47,8 +63,7 @@ def decrypt_ncryptsec(ncryptsec: str, password: str) -> bytes: aad = raw[42:43] ciphertext = raw[43:] n = 1 << log_n - # OpenSSL scrypt enforces a max memory bound; raise for large log_n (e.g. 20 ~= 1 GiB). - maxmem = max(256 * 1024 * 1024, 128 * n * _SCRYPT_R) + maxmem = _scrypt_maxmem_bytes(n, _SCRYPT_R) key = hashlib.scrypt( _normalize_password(password), salt=salt, @@ -72,7 +87,7 @@ def encrypt_to_ncryptsec(secret_key: bytes, password: str, log_n: int = _DEFAULT nonce = os.urandom(24) aad = bytes([KEY_SECURITY_MEDIUM]) n = 1 << log_n - maxmem = max(256 * 1024 * 1024, 128 * n * _SCRYPT_R) + maxmem = _scrypt_maxmem_bytes(n, _SCRYPT_R) sym = hashlib.scrypt( _normalize_password(password), salt=salt,