{"id":"99836e5035a937c86c28b4c824e570c356d1c2a0d1d572e6fcffb9ac773f3dc8","pubkey":"2e17b86d692c979bb5b7c2ddb9f70e02e76bbc358afa95ac68135047bbe10e02","created_at":1782724308,"kind":30817,"tags":[["d","rfprfc-turning-a-nostr-nsec-into-a-hardened-non-taproot-address-"],["title","RFP/RFC: Turning a Nostr Nsec Into a Hardened Non-Taproot Address... for on-chain zaps & CRQC resistance"],["alt","Nostr Implementation Possibility: RFP/RFC: Turning a Nostr Nsec Into a Hardened Non-Taproot Address... for on-chain zaps & CRQC resistance"],["k","0","profile metadata for bitcoin"],["k","9734","Zap Request"],["k","9735","Zap Receipt"],["k","30017","marketplace orders"],["client","NostrHub"]],"content":"Preface: I put the kinds Gemini suggested, but maybe need to define a new kind, as IIRC Vitor had criticized Kind 0 bloat.\n\nCopied from my 2360 issue on GitHub:\n\nIn the end, I want nostriches to be optionally able to receive zaps to safe(r)-at-rest public key hashed addresses that are derived user/client (NOT server) side from their nsecs + passphrases of their choice (with only the slightest extra step of putting in their passphrase), versus unsafe-at-rest Taproot addresses (which don't need a passphrase at all in the current form of \"on-chain zaps\"). \n\nThis option deliberately breaks the UI flow to any unhashed key derivations from the npub/nsec; the hardened form will need to be indicated as a payment target or something that points would-be zappers to it, away from any unhardened derivations. It also presumes the receiver isn't in need of 24/7 guardianship/care & is therefore able to practice proper hygiene unassisted, never letting their public zap (fka tip) UTXOs move with/near their privately-transacted UTXOs & never revealing their pubkey until it's balance-zeroing-TX/no spending reuse of the key time (as well as not completely losing their nsec+passphrase & all those UTXOs in turn).\n\nAnyone with me/who can do this better/at all?\n\n[This is from the middle](https://ditto.pub/naddr1qvzqqqr4gupzqtshhpkkjtyhnw6m0skah8msuqh8dw7rtzh6jkkxsy6sg7a7zrszqpzxsmmh946x7tt5w4exuttp94hx7um5wgkkuum9vvkkjmn5dukkzttgv9exgetwv4jz6mn0dckhgctswfhk7apdv9jxgun9wdej66tw94c8ymmy46xjya):\n\nThanks again to ChatGPT, if you're concerned about CRQC coming true & compromising your UTXOs under the exposed pubkey of even-y derived p2pkh & p2wpkh addresses from your Nostr nsec/npub...\n\n(And obviously, if you're concerned about CRQC at all, you aren't touching Taproot/p2tr/bc1p...)\n\nHere's how you take your nsec & fight CRQC with \"perfectly okay\" HKDF-SHA256 to get a passphrase-salted keypair:\n\n```\n#\n# Completely dependency-free single-file script\n#\n# Features:\n# - Decode Nostr nsec\n# - Derive hardened BTC private key from:\n#       nsec + passphrase\n# - Deterministic HKDF-SHA256 derivation\n# - secp256k1 public key derivation\n# - WIF generation\n# - P2PKH address\n# - P2WPKH address\n#\n# No pip installs required.\n#\n# Uses only:\n#   hashlib\n#   hmac\n#   secrets\n#\n# WARNING:\n# This is educational/minimal code.\n# Do not trust large funds to unaudited crypto code.\n#\n\nimport hashlib\nimport hmac\n\n\n# ============================================================\n# Base58\n# ============================================================\n\nBASE58_ALPHABET = \"123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz\"\n\n\ndef b58encode(b):\n\n    n = int.from_bytes(b, \"big\")\n\n    out = \"\"\n\n    while n &gt; 0:\n        n, r = divmod(n, 58)\n        out = BASE58_ALPHABET[r] + out\n\n    pad = 0\n\n    for c in b:\n        if c == 0:\n            pad += 1\n        else:\n            break\n\n    return \"1\" * pad + out\n\n\n# ============================================================\n# Bech32\n# ============================================================\n\nBECH32_CHARSET = \"qpzry9x8gf2tvdw0s3jn54khce6mua7l\"\n\n\ndef bech32_polymod(values):\n\n    GEN = [\n        0x3b6a57b2,\n        0x26508e6d,\n        0x1ea119fa,\n        0x3d4233dd,\n        0x2a1462b3\n    ]\n\n    chk = 1\n\n    for v in values:\n\n        top = chk &gt;&gt; 25\n\n        chk = ((chk & 0x1ffffff) &lt;&lt; 5) ^ v\n\n        for i in range(5):\n            if ((top &gt;&gt; i) & 1):\n                chk ^= GEN[i]\n\n    return chk\n\n\ndef bech32_hrp_expand(hrp):\n\n    return [ord(x) &gt;&gt; 5 for x in hrp] + [0] + [\n        ord(x) & 31 for x in hrp\n    ]\n\n\ndef bech32_verify_checksum(hrp, data):\n\n    return bech32_polymod(\n        bech32_hrp_expand(hrp) + data\n    ) == 1\n\n\ndef bech32_decode(bech):\n\n    bech = bech.lower()\n\n    pos = bech.rfind(\"1\")\n\n    if pos &lt; 1:\n        return None, None\n\n    hrp = bech[:pos]\n\n    data = []\n\n    for c in bech[pos + 1:]:\n\n        if c not in BECH32_CHARSET:\n            return None, None\n\n        data.append(\n            BECH32_CHARSET.find(c)\n        )\n\n    if not bech32_verify_checksum(hrp, data):\n        return None, None\n\n    return hrp, data[:-6]\n\n\ndef bech32_create_checksum(hrp, data):\n\n    values = bech32_hrp_expand(hrp) + data\n\n    polymod = bech32_polymod(\n        values + [0, 0, 0, 0, 0, 0]\n    ) ^ 1\n\n    return [\n        (polymod &gt;&gt; 5 * (5 - i)) & 31\n        for i in range(6)\n    ]\n\n\ndef bech32_encode(hrp, data):\n\n    combined = data + bech32_create_checksum(\n        hrp,\n        data\n    )\n\n    return hrp + \"1\" + \"\".join(\n        [BECH32_CHARSET[d] for d in combined]\n    )\n\n\ndef convertbits(data, frombits, tobits, pad=True):\n\n    acc = 0\n    bits = 0\n    ret = []\n\n    maxv = (1 &lt;&lt; tobits) - 1\n\n    for value in data:\n\n        acc = (acc &lt;&lt; frombits) | value\n        bits += frombits\n\n        while bits &gt;= tobits:\n\n            bits -= tobits\n\n            ret.append(\n                (acc &gt;&gt; bits) & maxv\n            )\n\n    if pad:\n\n        if bits:\n            ret.append(\n                (acc &lt;&lt; (tobits - bits)) & maxv\n            )\n\n    elif bits &gt;= frombits or (\n        (acc &lt;&lt; (tobits - bits)) & maxv\n    ):\n        return None\n\n    return ret\n\n\n# ============================================================\n# secp256k1\n# ============================================================\n\nP = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F\nN = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141\n\nGX = 55066263022277343669578718895168534326250603453777594175500187360389116729240\nGY = 32670510020758816978083085130507043184471273380659243275938904335757337482424\n\n\ndef inverse_mod(a, p):\n\n    return pow(a, p - 2, p)\n\n\ndef point_add(p1, p2):\n\n    if p1 is None:\n        return p2\n\n    if p2 is None:\n        return p1\n\n    x1, y1 = p1\n    x2, y2 = p2\n\n    if x1 == x2 and y1 != y2:\n        return None\n\n    if p1 == p2:\n\n        m = (\n            (3 * x1 * x1)\n            * inverse_mod(2 * y1, P)\n        ) % P\n\n    else:\n\n        m = (\n            (y2 - y1)\n            * inverse_mod(x2 - x1, P)\n        ) % P\n\n    x3 = (m * m - x1 - x2) % P\n    y3 = (m * (x1 - x3) - y1) % P\n\n    return (x3, y3)\n\n\ndef scalar_mult(k, point):\n\n    result = None\n\n    addend = point\n\n    while k:\n\n        if k & 1:\n            result = point_add(result, addend)\n\n        addend = point_add(addend, addend)\n\n        k &gt;&gt;= 1\n\n    return result\n\n# ============================================================\n# Pure Python RIPEMD160\n# ============================================================\n\ndef _rol(x, n):\n    return ((x &lt;&lt; n) | (x &gt;&gt; (32 - n))) & 0xffffffff\n\n\ndef ripemd160(msg):\n\n    # --------------------------------------------------------\n    # Constants\n    # --------------------------------------------------------\n\n    r1 = [\n         0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14,15,\n         7, 4,13, 1,10, 6,15, 3,12, 0, 9, 5, 2,14,11, 8,\n         3,10,14, 4, 9,15, 8, 1, 2, 7, 0, 6,13,11, 5,12,\n         1, 9,11,10, 0, 8,12, 4,13, 3, 7,15,14, 5, 6, 2,\n         4, 0, 5, 9, 7,12, 2,10,14, 1, 3, 8,11, 6,15,13\n    ]\n\n    r2 = [\n         5,14, 7, 0, 9, 2,11, 4,13, 6,15, 8, 1,10, 3,12,\n         6,11, 3, 7, 0,13, 5,10,14,15, 8,12, 4, 9, 1, 2,\n        15, 5, 1, 3, 7,14, 6, 9,11, 8,12, 2,10, 0, 4,13,\n         8, 6, 4, 1, 3,11,15, 0, 5,12, 2,13, 9, 7,10,14,\n        12,15,10, 4, 1, 5, 8, 7, 6, 2,13,14, 0, 3, 9,11\n    ]\n\n    s1 = [\n        11,14,15,12, 5, 8, 7, 9,11,13,14,15, 6, 7, 9, 8,\n         7, 6, 8,13,11, 9, 7,15, 7,12,15, 9,11, 7,13,12,\n        11,13, 6, 7,14, 9,13,15,14, 8,13, 6, 5,12, 7, 5,\n        11,12,14,15,14,15, 9, 8, 9,14, 5, 6, 8, 6, 5,12,\n         9,15, 5,11, 6, 8,13,12, 5,12,13,14,11, 8, 5, 6\n    ]\n\n    s2 = [\n         8, 9, 9,11,13,15,15, 5, 7, 7, 8,11,14,14,12, 6,\n         9,13,15, 7,12, 8, 9,11, 7, 7,12, 7, 6,15,13,11,\n         9, 7,15,11, 8, 6, 6,14,12,13, 5,14,13,13, 7, 5,\n        15, 5, 8,11,14,14, 6,14, 6, 9,12, 9,12, 5,15, 8,\n         8, 5,12, 9,12, 5,14, 6, 8,13, 6, 5,15,13,11,11\n    ]\n\n    # --------------------------------------------------------\n    # Functions\n    # --------------------------------------------------------\n\n    def f(j, x, y, z):\n\n        if 0 &lt;= j &lt;= 15:\n            return x ^ y ^ z\n\n        if 16 &lt;= j &lt;= 31:\n            return (x & y) | (~x & z)\n\n        if 32 &lt;= j &lt;= 47:\n            return (x | ~y) ^ z\n\n        if 48 &lt;= j &lt;= 63:\n            return (x & z) | (y & ~z)\n\n        return x ^ (y | ~z)\n\n    def K1(j):\n\n        if 0 &lt;= j &lt;= 15:\n            return 0x00000000\n\n        if 16 &lt;= j &lt;= 31:\n            return 0x5A827999\n\n        if 32 &lt;= j &lt;= 47:\n            return 0x6ED9EBA1\n\n        if 48 &lt;= j &lt;= 63:\n            return 0x8F1BBCDC\n\n        return 0xA953FD4E\n\n    def K2(j):\n\n        if 0 &lt;= j &lt;= 15:\n            return 0x50A28BE6\n\n        if 16 &lt;= j &lt;= 31:\n            return 0x5C4DD124\n\n        if 32 &lt;= j &lt;= 47:\n            return 0x6D703EF3\n\n        if 48 &lt;= j &lt;= 63:\n            return 0x7A6D76E9\n\n        return 0x00000000\n\n    # --------------------------------------------------------\n    # Padding\n    # --------------------------------------------------------\n\n    ml = len(msg) * 8\n\n    msg += b\"\\x80\"\n\n    while (len(msg) % 64) != 56:\n        msg += b\"\\x00\"\n\n    msg += ml.to_bytes(8, \"little\")\n\n    # --------------------------------------------------------\n    # Initial state\n    # --------------------------------------------------------\n\n    h0 = 0x67452301\n    h1 = 0xEFCDAB89\n    h2 = 0x98BADCFE\n    h3 = 0x10325476\n    h4 = 0xC3D2E1F0\n\n    # --------------------------------------------------------\n    # Process blocks\n    # --------------------------------------------------------\n\n    for offset in range(0, len(msg), 64):\n\n        block = msg[offset:offset + 64]\n\n        X = [\n            int.from_bytes(\n                block[i:i+4],\n                \"little\"\n            )\n            for i in range(0, 64, 4)\n        ]\n\n        A1 = h0\n        B1 = h1\n        C1 = h2\n        D1 = h3\n        E1 = h4\n\n        A2 = h0\n        B2 = h1\n        C2 = h2\n        D2 = h3\n        E2 = h4\n\n        for j in range(80):\n\n            T = (\n                _rol(\n                    (\n                        A1\n                        + f(j, B1, C1, D1)\n                        + X[r1[j]]\n                        + K1(j)\n                    ) & 0xffffffff,\n                    s1[j]\n                )\n                + E1\n            ) & 0xffffffff\n\n            A1, E1, D1, C1, B1 = (\n                E1,\n                D1,\n                _rol(C1, 10),\n                B1,\n                T\n            )\n\n            T = (\n                _rol(\n                    (\n                        A2\n                        + f(79 - j, B2, C2, D2)\n                        + X[r2[j]]\n                        + K2(j)\n                    ) & 0xffffffff,\n                    s2[j]\n                )\n                + E2\n            ) & 0xffffffff\n\n            A2, E2, D2, C2, B2 = (\n                E2,\n                D2,\n                _rol(C2, 10),\n                B2,\n                T\n            )\n\n        T = (h1 + C1 + D2) & 0xffffffff\n\n        h1 = (h2 + D1 + E2) & 0xffffffff\n        h2 = (h3 + E1 + A2) & 0xffffffff\n        h3 = (h4 + A1 + B2) & 0xffffffff\n        h4 = (h0 + B1 + C2) & 0xffffffff\n        h0 = T\n\n    return (\n        h0.to_bytes(4, \"little\")\n        + h1.to_bytes(4, \"little\")\n        + h2.to_bytes(4, \"little\")\n        + h3.to_bytes(4, \"little\")\n        + h4.to_bytes(4, \"little\")\n    )\n\n\n# ============================================================\n# HASH160\n# ============================================================\n\ndef hash160(data):\n\n    sha = hashlib.sha256(data).digest()\n\n    return ripemd160(sha)\n\n\n# ============================================================\n# Decode nsec\n# ============================================================\n\ndef nsec_to_privkey(nsec):\n\n    hrp, data = bech32_decode(nsec)\n\n    if hrp != \"nsec\":\n        raise ValueError(\"Invalid nsec\")\n\n    decoded = convertbits(\n        data,\n        5,\n        8,\n        False\n    )\n\n    if decoded is None:\n        raise ValueError(\"Bad convertbits\")\n\n    raw = bytes(decoded)\n\n    if len(raw) != 32:\n        raise ValueError(\"Expected 32-byte key\")\n\n    return raw\n\n\n# ============================================================\n# HKDF-SHA256\n# ============================================================\n\ndef hkdf_extract(salt, ikm):\n\n    return hmac.new(\n        salt,\n        ikm,\n        hashlib.sha256\n    ).digest()\n\n\ndef hkdf_expand(prk, info, length=32):\n\n    output = b\"\"\n\n    t = b\"\"\n\n    counter = 1\n\n    while len(output) &lt; length:\n\n        t = hmac.new(\n            prk,\n            t + info + bytes([counter]),\n            hashlib.sha256\n        ).digest()\n\n        output += t\n\n        counter += 1\n\n    return output[:length]\n\n\ndef derive_hardened_btc_privkey(\n    nsec,\n    passphrase\n):\n\n    nostr_privkey = nsec_to_privkey(\n        nsec\n    )\n\n    salt = hashlib.sha256(\n        passphrase.encode()\n    ).digest()\n\n    prk = hkdf_extract(\n        salt,\n        nostr_privkey\n    )\n\n    return hkdf_expand(\n        prk,\n        b\"nostr-to-bitcoin-v1\",\n        32\n    )\n\n\n# ============================================================\n# Pubkey\n# ============================================================\n\ndef privkey_to_pubkey(privkey_bytes):\n\n    k = int.from_bytes(\n        privkey_bytes,\n        \"big\"\n    )\n\n    x, y = scalar_mult(\n        k,\n        (GX, GY)\n    )\n\n    prefix = b\"\\x02\" if y % 2 == 0 else b\"\\x03\"\n\n    return prefix + x.to_bytes(32, \"big\")\n\n\n# ============================================================\n# WIF\n# ============================================================\n\ndef privkey_to_wif(privkey):\n\n    payload = (\n        b\"\\x80\"\n        + privkey\n        + b\"\\x01\"\n    )\n\n    checksum = hashlib.sha256(\n        hashlib.sha256(payload).digest()\n    ).digest()[:4]\n\n    return b58encode(\n        payload + checksum\n    )\n\n\n# ============================================================\n# P2PKH\n# ============================================================\n\ndef pubkey_to_p2pkh(pubkey):\n\n    h160 = hash160(pubkey)\n\n    payload = b\"\\x00\" + h160\n\n    checksum = hashlib.sha256(\n        hashlib.sha256(payload).digest()\n    ).digest()[:4]\n\n    return b58encode(\n        payload + checksum\n    )\n\n\n# ============================================================\n# P2WPKH\n# ============================================================\n\ndef pubkey_to_p2wpkh(pubkey):\n\n    h160 = hash160(pubkey)\n\n    data = [0] + convertbits(\n        h160,\n        8,\n        5\n    )\n\n    return bech32_encode(\n        \"bc\",\n        data\n    )\n\n\n# ============================================================\n# MAIN\n# ============================================================\n\nif __name__ == \"__main__\":\n\n    my_nsec = \"YOUR_NSEC_HERE\"\n\n    passphrase = \"correct horse battery staple\"\n\n    btc_privkey = derive_hardened_btc_privkey(\n        my_nsec,\n        passphrase\n    )\n\n    wif = privkey_to_wif(\n        btc_privkey\n    )\n\n    pubkey = privkey_to_pubkey(\n        btc_privkey\n    )\n\n    p2pkh = pubkey_to_p2pkh(\n        pubkey\n    )\n\n    p2wpkh = pubkey_to_p2wpkh(\n        pubkey\n    )\n\n    print()\n    print(\"===== Hardened BTC Derivation =====\")\n    print()\n\n    print(\"WIF:\")\n    print(wif)\n    print()\n\n    print(\"Compressed Public Key:\")\n    print(pubkey.hex())\n    print()\n\n    print(\"P2PKH:\")\n    print(p2pkh)\n    print()\n\n    print(\"P2WPKH:\")\n    print(p2wpkh)\n    print()\n```\n\nAnd Argon2id, better against GPU/ASIC cracking (install dependencies: pip install base58 bech32 ecdsa argon2-cffi):\n\n```\nimport hashlib\nimport base58\n\nfrom bech32 import bech32_encode, bech32_decode, convertbits\nfrom ecdsa import SigningKey, SECP256k1\n\nfrom argon2.low_level import hash_secret_raw, Type\n\n\n# ============================================================\n# Decode Nostr nsec\n# ============================================================\n\ndef nsec_to_privkey(nsec_str):\n    hrp, data5 = bech32_decode(nsec_str)\n\n    if hrp != \"nsec\":\n        raise ValueError(\"Invalid nsec\")\n\n    return bytes(convertbits(data5, 5, 8, False))\n\n\n# ============================================================\n# Argon2id hardened derivation\n# ============================================================\n\ndef derive_hardened_btc_privkey(nsec_str, passphrase):\n\n    nostr_privkey = nsec_to_privkey(nsec_str)\n\n    # Domain separation\n    domain = b\"nostr-to-bitcoin-v1\"\n\n    # Salt for Argon2id\n    #\n    # Deterministic:\n    # same nsec + same passphrase =&gt; same wallet\n    #\n    salt = hashlib.sha256(\n        domain + passphrase.encode()\n    ).digest()\n\n    # Argon2id derivation\n    #\n    # memory_cost is in KiB\n    #\n    derived_key = hash_secret_raw(\n        secret=nostr_privkey,\n        salt=salt,\n        time_cost=6,\n        memory_cost=262144,   # 256 MiB\n        parallelism=1,\n        hash_len=32,\n        type=Type.ID\n    )\n\n    return derived_key\n\n\n# ============================================================\n# Pure Python RIPEMD160\n# ============================================================\n\ndef _rol(x, n):\n    return ((x &lt;&lt; n) | (x &gt;&gt; (32 - n))) & 0xffffffff\n\n\ndef ripemd160(msg):\n\n    # --------------------------------------------------------\n    # Constants\n    # --------------------------------------------------------\n\n    r1 = [\n         0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14,15,\n         7, 4,13, 1,10, 6,15, 3,12, 0, 9, 5, 2,14,11, 8,\n         3,10,14, 4, 9,15, 8, 1, 2, 7, 0, 6,13,11, 5,12,\n         1, 9,11,10, 0, 8,12, 4,13, 3, 7,15,14, 5, 6, 2,\n         4, 0, 5, 9, 7,12, 2,10,14, 1, 3, 8,11, 6,15,13\n    ]\n\n    r2 = [\n         5,14, 7, 0, 9, 2,11, 4,13, 6,15, 8, 1,10, 3,12,\n         6,11, 3, 7, 0,13, 5,10,14,15, 8,12, 4, 9, 1, 2,\n        15, 5, 1, 3, 7,14, 6, 9,11, 8,12, 2,10, 0, 4,13,\n         8, 6, 4, 1, 3,11,15, 0, 5,12, 2,13, 9, 7,10,14,\n        12,15,10, 4, 1, 5, 8, 7, 6, 2,13,14, 0, 3, 9,11\n    ]\n\n    s1 = [\n        11,14,15,12, 5, 8, 7, 9,11,13,14,15, 6, 7, 9, 8,\n         7, 6, 8,13,11, 9, 7,15, 7,12,15, 9,11, 7,13,12,\n        11,13, 6, 7,14, 9,13,15,14, 8,13, 6, 5,12, 7, 5,\n        11,12,14,15,14,15, 9, 8, 9,14, 5, 6, 8, 6, 5,12,\n         9,15, 5,11, 6, 8,13,12, 5,12,13,14,11, 8, 5, 6\n    ]\n\n    s2 = [\n         8, 9, 9,11,13,15,15, 5, 7, 7, 8,11,14,14,12, 6,\n         9,13,15, 7,12, 8, 9,11, 7, 7,12, 7, 6,15,13,11,\n         9, 7,15,11, 8, 6, 6,14,12,13, 5,14,13,13, 7, 5,\n        15, 5, 8,11,14,14, 6,14, 6, 9,12, 9,12, 5,15, 8,\n         8, 5,12, 9,12, 5,14, 6, 8,13, 6, 5,15,13,11,11\n    ]\n\n    # --------------------------------------------------------\n    # Functions\n    # --------------------------------------------------------\n\n    def f(j, x, y, z):\n\n        if 0 &lt;= j &lt;= 15:\n            return x ^ y ^ z\n\n        if 16 &lt;= j &lt;= 31:\n            return (x & y) | (~x & z)\n\n        if 32 &lt;= j &lt;= 47:\n            return (x | ~y) ^ z\n\n        if 48 &lt;= j &lt;= 63:\n            return (x & z) | (y & ~z)\n\n        return x ^ (y | ~z)\n\n    def K1(j):\n\n        if 0 &lt;= j &lt;= 15:\n            return 0x00000000\n\n        if 16 &lt;= j &lt;= 31:\n            return 0x5A827999\n\n        if 32 &lt;= j &lt;= 47:\n            return 0x6ED9EBA1\n\n        if 48 &lt;= j &lt;= 63:\n            return 0x8F1BBCDC\n\n        return 0xA953FD4E\n\n    def K2(j):\n\n        if 0 &lt;= j &lt;= 15:\n            return 0x50A28BE6\n\n        if 16 &lt;= j &lt;= 31:\n            return 0x5C4DD124\n\n        if 32 &lt;= j &lt;= 47:\n            return 0x6D703EF3\n\n        if 48 &lt;= j &lt;= 63:\n            return 0x7A6D76E9\n\n        return 0x00000000\n\n    # --------------------------------------------------------\n    # Padding\n    # --------------------------------------------------------\n\n    ml = len(msg) * 8\n\n    msg += b\"\\x80\"\n\n    while (len(msg) % 64) != 56:\n        msg += b\"\\x00\"\n\n    msg += ml.to_bytes(8, \"little\")\n\n    # --------------------------------------------------------\n    # Initial state\n    # --------------------------------------------------------\n\n    h0 = 0x67452301\n    h1 = 0xEFCDAB89\n    h2 = 0x98BADCFE\n    h3 = 0x10325476\n    h4 = 0xC3D2E1F0\n\n    # --------------------------------------------------------\n    # Process blocks\n    # --------------------------------------------------------\n\n    for offset in range(0, len(msg), 64):\n\n        block = msg[offset:offset + 64]\n\n        X = [\n            int.from_bytes(\n                block[i:i+4],\n                \"little\"\n            )\n            for i in range(0, 64, 4)\n        ]\n\n        A1 = h0\n        B1 = h1\n        C1 = h2\n        D1 = h3\n        E1 = h4\n\n        A2 = h0\n        B2 = h1\n        C2 = h2\n        D2 = h3\n        E2 = h4\n\n        for j in range(80):\n\n            T = (\n                _rol(\n                    (\n                        A1\n                        + f(j, B1, C1, D1)\n                        + X[r1[j]]\n                        + K1(j)\n                    ) & 0xffffffff,\n                    s1[j]\n                )\n                + E1\n            ) & 0xffffffff\n\n            A1, E1, D1, C1, B1 = (\n                E1,\n                D1,\n                _rol(C1, 10),\n                B1,\n                T\n            )\n\n            T = (\n                _rol(\n                    (\n                        A2\n                        + f(79 - j, B2, C2, D2)\n                        + X[r2[j]]\n                        + K2(j)\n                    ) & 0xffffffff,\n                    s2[j]\n                )\n                + E2\n            ) & 0xffffffff\n\n            A2, E2, D2, C2, B2 = (\n                E2,\n                D2,\n                _rol(C2, 10),\n                B2,\n                T\n            )\n\n        T = (h1 + C1 + D2) & 0xffffffff\n\n        h1 = (h2 + D1 + E2) & 0xffffffff\n        h2 = (h3 + E1 + A2) & 0xffffffff\n        h3 = (h4 + A1 + B2) & 0xffffffff\n        h4 = (h0 + B1 + C2) & 0xffffffff\n        h0 = T\n\n    return (\n        h0.to_bytes(4, \"little\")\n        + h1.to_bytes(4, \"little\")\n        + h2.to_bytes(4, \"little\")\n        + h3.to_bytes(4, \"little\")\n        + h4.to_bytes(4, \"little\")\n    )\n\n# ============================================================\n# HASH160\n# ============================================================\n\ndef hash160(data):\n\n    sha = hashlib.sha256(data).digest()\n\n    return ripemd160(sha)\n\n\n# ============================================================\n# Compressed SEC pubkey\n# ============================================================\n\ndef privkey_to_compressed_pubkey(privkey_bytes):\n\n    sk = SigningKey.from_string(\n        privkey_bytes,\n        curve=SECP256k1\n    )\n\n    vk = sk.verifying_key\n\n    x = vk.pubkey.point.x()\n    y = vk.pubkey.point.y()\n\n    prefix = b\"\\x02\" if y % 2 == 0 else b\"\\x03\"\n\n    return prefix + x.to_bytes(32, \"big\")\n\n\n# ============================================================\n# WIF\n# ============================================================\n\ndef privkey_to_wif(privkey_bytes, compressed=True):\n\n    payload = b\"\\x80\" + privkey_bytes\n\n    if compressed:\n        payload += b\"\\x01\"\n\n    checksum = hashlib.sha256(\n        hashlib.sha256(payload).digest()\n    ).digest()[:4]\n\n    return base58.b58encode(\n        payload + checksum\n    ).decode()\n\n\n# ============================================================\n# P2PKH\n# ============================================================\n\ndef pubkey_to_p2pkh(pubkey_bytes):\n\n    h160 = hash160(pubkey_bytes)\n\n    payload = b\"\\x00\" + h160\n\n    checksum = hashlib.sha256(\n        hashlib.sha256(payload).digest()\n    ).digest()[:4]\n\n    return base58.b58encode(\n        payload + checksum\n    ).decode()\n\n\n# ============================================================\n# Native SegWit P2WPKH\n# ============================================================\n\ndef pubkey_to_p2wpkh(pubkey_bytes):\n\n    h160 = hash160(pubkey_bytes)\n\n    data = [0] + convertbits(h160, 8, 5)\n\n    return bech32_encode(\"bc\", data)\n\n\n# ============================================================\n# MAIN\n# ============================================================\n\nif __name__ == \"__main__\":\n\n    my_nsec = \"YOUR_NSEC_HERE\"\n\n    passphrase = \"correct horse battery staple\"\n\n    # Derive hardened BTC private key\n    btc_privkey = derive_hardened_btc_privkey(\n        my_nsec,\n        passphrase\n    )\n\n    # WIF\n    wif = privkey_to_wif(btc_privkey)\n\n    # Compressed pubkey\n    pubkey = privkey_to_compressed_pubkey(\n        btc_privkey\n    )\n\n    # Addresses\n    p2pkh = pubkey_to_p2pkh(pubkey)\n\n    p2wpkh = pubkey_to_p2wpkh(pubkey)\n\n    # Output\n    print()\n    print(\"===== Hardened BTC Derivation =====\")\n    print()\n\n    print(\"WIF:\")\n    print(wif)\n    print()\n\n    print(\"Compressed Public Key:\")\n    print(pubkey.hex())\n    print()\n\n    print(\"P2PKH Address (Legacy):\")\n    print(p2pkh)\n    print()\n\n    print(\"P2WPKH Address (Native SegWit):\")\n    print(p2wpkh)\n    print()\n```\n\nPer ChatGPT:\n\nThe Argon2id parameters above are intentionally fairly expensive:\n\n```\ntime_cost=6\nmemory_cost=262144   # 256 MiB\n```\n\nBut you can tune them:\n\n| Device               | Suggested memory\\_cost |\n| -------------------- | ---------------------- |\n| low-RAM VPS          | 65536                  |\n| laptop/desktop       | 262144                 |\n| high-end workstation | 524288+                |\n\nThe passphrase is NOT merely “extra entropy”; it becomes part of the Argon2id salt namespace.\n\nThat means:\n\n* identical nsec\n\n* but different passphrase\n\nproduces completely unrelated Bitcoin wallets.\n\nAnd:\n\n* leaked npub\n\n* leaked nsec\n\n* future secp256k1 break\n\nstill do not reveal the Bitcoin private key without the passphrase.","sig":"f8dff7b988eb3ac7275e5a2f69e6e8ae2cdec4aabe35e3855b1913bd2a3e78f3eef1ff4feaf2009f3199299d8058c6d598d43aae430909ede9dd5524a86d6948"}