How To Turn a Nostr Nsec Into a Hardened Non-Taproot Address In Prod

Finally with passphrase salt code! The end of the npub/nsec to p2pkh/p2wpkh derivation series?
How To Turn a Nostr Nsec Into a Hardened Non-Taproot Address In Prod

Previously…
Thanks again to ChatGPT, if you’re concerned about QC coming true & compromising your UTXOs under the exposed pubkey of even-y derived p2pkh & p2wpkh addresses from your Nostr nsec/npub…

Here’s how you take your nsec & fight QC with “perfectly okay” HKDF-SHA256 to get a passphrase-salted keypair:

#
# Completely dependency-free single-file script
#
# Features:
# - Decode Nostr nsec
# - Derive hardened BTC private key from:
#       nsec + passphrase
# - Deterministic HKDF-SHA256 derivation
# - secp256k1 public key derivation
# - WIF generation
# - P2PKH address
# - P2WPKH address
#
# No pip installs required.
#
# Uses only:
#   hashlib
#   hmac
#   secrets
#
# WARNING:
# This is educational/minimal code.
# Do not trust large funds to unaudited crypto code.
#

import hashlib
import hmac


# ============================================================
# Base58
# ============================================================

BASE58_ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"


def b58encode(b):

    n = int.from_bytes(b, "big")

    out = ""

    while n > 0:
        n, r = divmod(n, 58)
        out = BASE58_ALPHABET[r] + out

    pad = 0

    for c in b:
        if c == 0:
            pad += 1
        else:
            break

    return "1" * pad + out


# ============================================================
# Bech32
# ============================================================

BECH32_CHARSET = "qpzry9x8gf2tvdw0s3jn54khce6mua7l"


def bech32_polymod(values):

    GEN = [
        0x3b6a57b2,
        0x26508e6d,
        0x1ea119fa,
        0x3d4233dd,
        0x2a1462b3
    ]

    chk = 1

    for v in values:

        top = chk >> 25

        chk = ((chk & 0x1ffffff) << 5) ^ v

        for i in range(5):
            if ((top >> i) & 1):
                chk ^= GEN[i]

    return chk


def bech32_hrp_expand(hrp):

    return [ord(x) >> 5 for x in hrp] + [0] + [
        ord(x) & 31 for x in hrp
    ]


def bech32_verify_checksum(hrp, data):

    return bech32_polymod(
        bech32_hrp_expand(hrp) + data
    ) == 1


def bech32_decode(bech):

    bech = bech.lower()

    pos = bech.rfind("1")

    if pos < 1:
        return None, None

    hrp = bech[:pos]

    data = []

    for c in bech[pos + 1:]:

        if c not in BECH32_CHARSET:
            return None, None

        data.append(
            BECH32_CHARSET.find(c)
        )

    if not bech32_verify_checksum(hrp, data):
        return None, None

    return hrp, data[:-6]


def bech32_create_checksum(hrp, data):

    values = bech32_hrp_expand(hrp) + data

    polymod = bech32_polymod(
        values + [0, 0, 0, 0, 0, 0]
    ) ^ 1

    return [
        (polymod >> 5 * (5 - i)) & 31
        for i in range(6)
    ]


def bech32_encode(hrp, data):

    combined = data + bech32_create_checksum(
        hrp,
        data
    )

    return hrp + "1" + "".join(
        [BECH32_CHARSET[d] for d in combined]
    )


def convertbits(data, frombits, tobits, pad=True):

    acc = 0
    bits = 0
    ret = []

    maxv = (1 << tobits) - 1

    for value in data:

        acc = (acc << frombits) | value
        bits += frombits

        while bits >= tobits:

            bits -= tobits

            ret.append(
                (acc >> bits) & maxv
            )

    if pad:

        if bits:
            ret.append(
                (acc << (tobits - bits)) & maxv
            )

    elif bits >= frombits or (
        (acc << (tobits - bits)) & maxv
    ):
        return None

    return ret


# ============================================================
# secp256k1
# ============================================================

P = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F
N = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141

GX = 55066263022277343669578718895168534326250603453777594175500187360389116729240
GY = 32670510020758816978083085130507043184471273380659243275938904335757337482424


def inverse_mod(a, p):

    return pow(a, p - 2, p)


def point_add(p1, p2):

    if p1 is None:
        return p2

    if p2 is None:
        return p1

    x1, y1 = p1
    x2, y2 = p2

    if x1 == x2 and y1 != y2:
        return None

    if p1 == p2:

        m = (
            (3 * x1 * x1)
            * inverse_mod(2 * y1, P)
        ) % P

    else:

        m = (
            (y2 - y1)
            * inverse_mod(x2 - x1, P)
        ) % P

    x3 = (m * m - x1 - x2) % P
    y3 = (m * (x1 - x3) - y1) % P

    return (x3, y3)


def scalar_mult(k, point):

    result = None

    addend = point

    while k:

        if k & 1:
            result = point_add(result, addend)

        addend = point_add(addend, addend)

        k >>= 1

    return result

# ============================================================
# Pure Python RIPEMD160
# ============================================================

def _rol(x, n):
    return ((x << n) | (x >> (32 - n))) & 0xffffffff


def ripemd160(msg):

    # --------------------------------------------------------
    # Constants
    # --------------------------------------------------------

    r1 = [
         0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14,15,
         7, 4,13, 1,10, 6,15, 3,12, 0, 9, 5, 2,14,11, 8,
         3,10,14, 4, 9,15, 8, 1, 2, 7, 0, 6,13,11, 5,12,
         1, 9,11,10, 0, 8,12, 4,13, 3, 7,15,14, 5, 6, 2,
         4, 0, 5, 9, 7,12, 2,10,14, 1, 3, 8,11, 6,15,13
    ]

    r2 = [
         5,14, 7, 0, 9, 2,11, 4,13, 6,15, 8, 1,10, 3,12,
         6,11, 3, 7, 0,13, 5,10,14,15, 8,12, 4, 9, 1, 2,
        15, 5, 1, 3, 7,14, 6, 9,11, 8,12, 2,10, 0, 4,13,
         8, 6, 4, 1, 3,11,15, 0, 5,12, 2,13, 9, 7,10,14,
        12,15,10, 4, 1, 5, 8, 7, 6, 2,13,14, 0, 3, 9,11
    ]

    s1 = [
        11,14,15,12, 5, 8, 7, 9,11,13,14,15, 6, 7, 9, 8,
         7, 6, 8,13,11, 9, 7,15, 7,12,15, 9,11, 7,13,12,
        11,13, 6, 7,14, 9,13,15,14, 8,13, 6, 5,12, 7, 5,
        11,12,14,15,14,15, 9, 8, 9,14, 5, 6, 8, 6, 5,12,
         9,15, 5,11, 6, 8,13,12, 5,12,13,14,11, 8, 5, 6
    ]

    s2 = [
         8, 9, 9,11,13,15,15, 5, 7, 7, 8,11,14,14,12, 6,
         9,13,15, 7,12, 8, 9,11, 7, 7,12, 7, 6,15,13,11,
         9, 7,15,11, 8, 6, 6,14,12,13, 5,14,13,13, 7, 5,
        15, 5, 8,11,14,14, 6,14, 6, 9,12, 9,12, 5,15, 8,
         8, 5,12, 9,12, 5,14, 6, 8,13, 6, 5,15,13,11,11
    ]

    # --------------------------------------------------------
    # Functions
    # --------------------------------------------------------

    def f(j, x, y, z):

        if 0 <= j <= 15:
            return x ^ y ^ z

        if 16 <= j <= 31:
            return (x & y) | (~x & z)

        if 32 <= j <= 47:
            return (x | ~y) ^ z

        if 48 <= j <= 63:
            return (x & z) | (y & ~z)

        return x ^ (y | ~z)

    def K1(j):

        if 0 <= j <= 15:
            return 0x00000000

        if 16 <= j <= 31:
            return 0x5A827999

        if 32 <= j <= 47:
            return 0x6ED9EBA1

        if 48 <= j <= 63:
            return 0x8F1BBCDC

        return 0xA953FD4E

    def K2(j):

        if 0 <= j <= 15:
            return 0x50A28BE6

        if 16 <= j <= 31:
            return 0x5C4DD124

        if 32 <= j <= 47:
            return 0x6D703EF3

        if 48 <= j <= 63:
            return 0x7A6D76E9

        return 0x00000000

    # --------------------------------------------------------
    # Padding
    # --------------------------------------------------------

    ml = len(msg) * 8

    msg += b"\x80"

    while (len(msg) % 64) != 56:
        msg += b"\x00"

    msg += ml.to_bytes(8, "little")

    # --------------------------------------------------------
    # Initial state
    # --------------------------------------------------------

    h0 = 0x67452301
    h1 = 0xEFCDAB89
    h2 = 0x98BADCFE
    h3 = 0x10325476
    h4 = 0xC3D2E1F0

    # --------------------------------------------------------
    # Process blocks
    # --------------------------------------------------------

    for offset in range(0, len(msg), 64):

        block = msg[offset:offset + 64]

        X = [
            int.from_bytes(
                block[i:i+4],
                "little"
            )
            for i in range(0, 64, 4)
        ]

        A1 = h0
        B1 = h1
        C1 = h2
        D1 = h3
        E1 = h4

        A2 = h0
        B2 = h1
        C2 = h2
        D2 = h3
        E2 = h4

        for j in range(80):

            T = (
                _rol(
                    (
                        A1
                        + f(j, B1, C1, D1)
                        + X[r1[j]]
                        + K1(j)
                    ) & 0xffffffff,
                    s1[j]
                )
                + E1
            ) & 0xffffffff

            A1, E1, D1, C1, B1 = (
                E1,
                D1,
                _rol(C1, 10),
                B1,
                T
            )

            T = (
                _rol(
                    (
                        A2
                        + f(79 - j, B2, C2, D2)
                        + X[r2[j]]
                        + K2(j)
                    ) & 0xffffffff,
                    s2[j]
                )
                + E2
            ) & 0xffffffff

            A2, E2, D2, C2, B2 = (
                E2,
                D2,
                _rol(C2, 10),
                B2,
                T
            )

        T = (h1 + C1 + D2) & 0xffffffff

        h1 = (h2 + D1 + E2) & 0xffffffff
        h2 = (h3 + E1 + A2) & 0xffffffff
        h3 = (h4 + A1 + B2) & 0xffffffff
        h4 = (h0 + B1 + C2) & 0xffffffff
        h0 = T

    return (
        h0.to_bytes(4, "little")
        + h1.to_bytes(4, "little")
        + h2.to_bytes(4, "little")
        + h3.to_bytes(4, "little")
        + h4.to_bytes(4, "little")
    )


# ============================================================
# HASH160
# ============================================================

def hash160(data):

    sha = hashlib.sha256(data).digest()

    return ripemd160(sha)


# ============================================================
# Decode nsec
# ============================================================

def nsec_to_privkey(nsec):

    hrp, data = bech32_decode(nsec)

    if hrp != "nsec":
        raise ValueError("Invalid nsec")

    decoded = convertbits(
        data,
        5,
        8,
        False
    )

    if decoded is None:
        raise ValueError("Bad convertbits")

    raw = bytes(decoded)

    if len(raw) != 32:
        raise ValueError("Expected 32-byte key")

    return raw


# ============================================================
# HKDF-SHA256
# ============================================================

def hkdf_extract(salt, ikm):

    return hmac.new(
        salt,
        ikm,
        hashlib.sha256
    ).digest()


def hkdf_expand(prk, info, length=32):

    output = b""

    t = b""

    counter = 1

    while len(output) < length:

        t = hmac.new(
            prk,
            t + info + bytes([counter]),
            hashlib.sha256
        ).digest()

        output += t

        counter += 1

    return output[:length]


def derive_hardened_btc_privkey(
    nsec,
    passphrase
):

    nostr_privkey = nsec_to_privkey(
        nsec
    )

    salt = hashlib.sha256(
        passphrase.encode()
    ).digest()

    prk = hkdf_extract(
        salt,
        nostr_privkey
    )

    return hkdf_expand(
        prk,
        b"nostr-to-bitcoin-v1",
        32
    )


# ============================================================
# Pubkey
# ============================================================

def privkey_to_pubkey(privkey_bytes):

    k = int.from_bytes(
        privkey_bytes,
        "big"
    )

    x, y = scalar_mult(
        k,
        (GX, GY)
    )

    prefix = b"\x02" if y % 2 == 0 else b"\x03"

    return prefix + x.to_bytes(32, "big")


# ============================================================
# WIF
# ============================================================

def privkey_to_wif(privkey):

    payload = (
        b"\x80"
        + privkey
        + b"\x01"
    )

    checksum = hashlib.sha256(
        hashlib.sha256(payload).digest()
    ).digest()[:4]

    return b58encode(
        payload + checksum
    )


# ============================================================
# P2PKH
# ============================================================

def pubkey_to_p2pkh(pubkey):

    h160 = hash160(pubkey)

    payload = b"\x00" + h160

    checksum = hashlib.sha256(
        hashlib.sha256(payload).digest()
    ).digest()[:4]

    return b58encode(
        payload + checksum
    )


# ============================================================
# P2WPKH
# ============================================================

def pubkey_to_p2wpkh(pubkey):

    h160 = hash160(pubkey)

    data = [0] + convertbits(
        h160,
        8,
        5
    )

    return bech32_encode(
        "bc",
        data
    )


# ============================================================
# MAIN
# ============================================================

if __name__ == "__main__":

    my_nsec = "YOUR_NSEC_HERE"

    passphrase = "correct horse battery staple"

    btc_privkey = derive_hardened_btc_privkey(
        my_nsec,
        passphrase
    )

    wif = privkey_to_wif(
        btc_privkey
    )

    pubkey = privkey_to_pubkey(
        btc_privkey
    )

    p2pkh = pubkey_to_p2pkh(
        pubkey
    )

    p2wpkh = pubkey_to_p2wpkh(
        pubkey
    )

    print()
    print("===== Hardened BTC Derivation =====")
    print()

    print("WIF:")
    print(wif)
    print()

    print("Compressed Public Key:")
    print(pubkey.hex())
    print()

    print("P2PKH:")
    print(p2pkh)
    print()

    print("P2WPKH:")
    print(p2wpkh)
    print()

And Argon2id, better against GPU/ASIC cracking (install dependencies: pip install base58 bech32 ecdsa argon2-cffi):

import hashlib
import base58

from bech32 import bech32_encode, bech32_decode, convertbits
from ecdsa import SigningKey, SECP256k1

from argon2.low_level import hash_secret_raw, Type


# ============================================================
# Decode Nostr nsec
# ============================================================

def nsec_to_privkey(nsec_str):
    hrp, data5 = bech32_decode(nsec_str)

    if hrp != "nsec":
        raise ValueError("Invalid nsec")

    return bytes(convertbits(data5, 5, 8, False))


# ============================================================
# Argon2id hardened derivation
# ============================================================

def derive_hardened_btc_privkey(nsec_str, passphrase):

    nostr_privkey = nsec_to_privkey(nsec_str)

    # Domain separation
    domain = b"nostr-to-bitcoin-v1"

    # Salt for Argon2id
    #
    # Deterministic:
    # same nsec + same passphrase => same wallet
    #
    salt = hashlib.sha256(
        domain + passphrase.encode()
    ).digest()

    # Argon2id derivation
    #
    # memory_cost is in KiB
    #
    derived_key = hash_secret_raw(
        secret=nostr_privkey,
        salt=salt,
        time_cost=6,
        memory_cost=262144,   # 256 MiB
        parallelism=1,
        hash_len=32,
        type=Type.ID
    )

    return derived_key


# ============================================================
# Pure Python RIPEMD160
# ============================================================

def _rol(x, n):
    return ((x << n) | (x >> (32 - n))) & 0xffffffff


def ripemd160(msg):

    # --------------------------------------------------------
    # Constants
    # --------------------------------------------------------

    r1 = [
         0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14,15,
         7, 4,13, 1,10, 6,15, 3,12, 0, 9, 5, 2,14,11, 8,
         3,10,14, 4, 9,15, 8, 1, 2, 7, 0, 6,13,11, 5,12,
         1, 9,11,10, 0, 8,12, 4,13, 3, 7,15,14, 5, 6, 2,
         4, 0, 5, 9, 7,12, 2,10,14, 1, 3, 8,11, 6,15,13
    ]

    r2 = [
         5,14, 7, 0, 9, 2,11, 4,13, 6,15, 8, 1,10, 3,12,
         6,11, 3, 7, 0,13, 5,10,14,15, 8,12, 4, 9, 1, 2,
        15, 5, 1, 3, 7,14, 6, 9,11, 8,12, 2,10, 0, 4,13,
         8, 6, 4, 1, 3,11,15, 0, 5,12, 2,13, 9, 7,10,14,
        12,15,10, 4, 1, 5, 8, 7, 6, 2,13,14, 0, 3, 9,11
    ]

    s1 = [
        11,14,15,12, 5, 8, 7, 9,11,13,14,15, 6, 7, 9, 8,
         7, 6, 8,13,11, 9, 7,15, 7,12,15, 9,11, 7,13,12,
        11,13, 6, 7,14, 9,13,15,14, 8,13, 6, 5,12, 7, 5,
        11,12,14,15,14,15, 9, 8, 9,14, 5, 6, 8, 6, 5,12,
         9,15, 5,11, 6, 8,13,12, 5,12,13,14,11, 8, 5, 6
    ]

    s2 = [
         8, 9, 9,11,13,15,15, 5, 7, 7, 8,11,14,14,12, 6,
         9,13,15, 7,12, 8, 9,11, 7, 7,12, 7, 6,15,13,11,
         9, 7,15,11, 8, 6, 6,14,12,13, 5,14,13,13, 7, 5,
        15, 5, 8,11,14,14, 6,14, 6, 9,12, 9,12, 5,15, 8,
         8, 5,12, 9,12, 5,14, 6, 8,13, 6, 5,15,13,11,11
    ]

    # --------------------------------------------------------
    # Functions
    # --------------------------------------------------------

    def f(j, x, y, z):

        if 0 <= j <= 15:
            return x ^ y ^ z

        if 16 <= j <= 31:
            return (x & y) | (~x & z)

        if 32 <= j <= 47:
            return (x | ~y) ^ z

        if 48 <= j <= 63:
            return (x & z) | (y & ~z)

        return x ^ (y | ~z)

    def K1(j):

        if 0 <= j <= 15:
            return 0x00000000

        if 16 <= j <= 31:
            return 0x5A827999

        if 32 <= j <= 47:
            return 0x6ED9EBA1

        if 48 <= j <= 63:
            return 0x8F1BBCDC

        return 0xA953FD4E

    def K2(j):

        if 0 <= j <= 15:
            return 0x50A28BE6

        if 16 <= j <= 31:
            return 0x5C4DD124

        if 32 <= j <= 47:
            return 0x6D703EF3

        if 48 <= j <= 63:
            return 0x7A6D76E9

        return 0x00000000

    # --------------------------------------------------------
    # Padding
    # --------------------------------------------------------

    ml = len(msg) * 8

    msg += b"\x80"

    while (len(msg) % 64) != 56:
        msg += b"\x00"

    msg += ml.to_bytes(8, "little")

    # --------------------------------------------------------
    # Initial state
    # --------------------------------------------------------

    h0 = 0x67452301
    h1 = 0xEFCDAB89
    h2 = 0x98BADCFE
    h3 = 0x10325476
    h4 = 0xC3D2E1F0

    # --------------------------------------------------------
    # Process blocks
    # --------------------------------------------------------

    for offset in range(0, len(msg), 64):

        block = msg[offset:offset + 64]

        X = [
            int.from_bytes(
                block[i:i+4],
                "little"
            )
            for i in range(0, 64, 4)
        ]

        A1 = h0
        B1 = h1
        C1 = h2
        D1 = h3
        E1 = h4

        A2 = h0
        B2 = h1
        C2 = h2
        D2 = h3
        E2 = h4

        for j in range(80):

            T = (
                _rol(
                    (
                        A1
                        + f(j, B1, C1, D1)
                        + X[r1[j]]
                        + K1(j)
                    ) & 0xffffffff,
                    s1[j]
                )
                + E1
            ) & 0xffffffff

            A1, E1, D1, C1, B1 = (
                E1,
                D1,
                _rol(C1, 10),
                B1,
                T
            )

            T = (
                _rol(
                    (
                        A2
                        + f(79 - j, B2, C2, D2)
                        + X[r2[j]]
                        + K2(j)
                    ) & 0xffffffff,
                    s2[j]
                )
                + E2
            ) & 0xffffffff

            A2, E2, D2, C2, B2 = (
                E2,
                D2,
                _rol(C2, 10),
                B2,
                T
            )

        T = (h1 + C1 + D2) & 0xffffffff

        h1 = (h2 + D1 + E2) & 0xffffffff
        h2 = (h3 + E1 + A2) & 0xffffffff
        h3 = (h4 + A1 + B2) & 0xffffffff
        h4 = (h0 + B1 + C2) & 0xffffffff
        h0 = T

    return (
        h0.to_bytes(4, "little")
        + h1.to_bytes(4, "little")
        + h2.to_bytes(4, "little")
        + h3.to_bytes(4, "little")
        + h4.to_bytes(4, "little")
    )

# ============================================================
# HASH160
# ============================================================

def hash160(data):

    sha = hashlib.sha256(data).digest()

    return ripemd160(sha)


# ============================================================
# Compressed SEC pubkey
# ============================================================

def privkey_to_compressed_pubkey(privkey_bytes):

    sk = SigningKey.from_string(
        privkey_bytes,
        curve=SECP256k1
    )

    vk = sk.verifying_key

    x = vk.pubkey.point.x()
    y = vk.pubkey.point.y()

    prefix = b"\x02" if y % 2 == 0 else b"\x03"

    return prefix + x.to_bytes(32, "big")


# ============================================================
# WIF
# ============================================================

def privkey_to_wif(privkey_bytes, compressed=True):

    payload = b"\x80" + privkey_bytes

    if compressed:
        payload += b"\x01"

    checksum = hashlib.sha256(
        hashlib.sha256(payload).digest()
    ).digest()[:4]

    return base58.b58encode(
        payload + checksum
    ).decode()


# ============================================================
# P2PKH
# ============================================================

def pubkey_to_p2pkh(pubkey_bytes):

    h160 = hash160(pubkey_bytes)

    payload = b"\x00" + h160

    checksum = hashlib.sha256(
        hashlib.sha256(payload).digest()
    ).digest()[:4]

    return base58.b58encode(
        payload + checksum
    ).decode()


# ============================================================
# Native SegWit P2WPKH
# ============================================================

def pubkey_to_p2wpkh(pubkey_bytes):

    h160 = hash160(pubkey_bytes)

    data = [0] + convertbits(h160, 8, 5)

    return bech32_encode("bc", data)


# ============================================================
# MAIN
# ============================================================

if __name__ == "__main__":

    my_nsec = "YOUR_NSEC_HERE"

    passphrase = "correct horse battery staple"

    # Derive hardened BTC private key
    btc_privkey = derive_hardened_btc_privkey(
        my_nsec,
        passphrase
    )

    # WIF
    wif = privkey_to_wif(btc_privkey)

    # Compressed pubkey
    pubkey = privkey_to_compressed_pubkey(
        btc_privkey
    )

    # Addresses
    p2pkh = pubkey_to_p2pkh(pubkey)

    p2wpkh = pubkey_to_p2wpkh(pubkey)

    # Output
    print()
    print("===== Hardened BTC Derivation =====")
    print()

    print("WIF:")
    print(wif)
    print()

    print("Compressed Public Key:")
    print(pubkey.hex())
    print()

    print("P2PKH Address (Legacy):")
    print(p2pkh)
    print()

    print("P2WPKH Address (Native SegWit):")
    print(p2wpkh)
    print()

Per ChatGPT:

The Argon2id parameters above are intentionally fairly expensive:

time_cost=6
memory_cost=262144   # 256 MiB

But you can tune them:

Device Suggested memory_cost
low-RAM VPS 65536
laptop/desktop 262144
high-end workstation 524288+

The passphrase is NOT merely “extra entropy”; it becomes part of the Argon2id salt namespace.

That means:

  • identical nsec

  • but different passphrase

produces completely unrelated Bitcoin wallets.

And:

  • leaked npub

  • leaked nsec

  • future secp256k1 break

still do not reveal the Bitcoin private key without the passphrase.

Is this the end of the series? Please let it be…


Write a comment
No comments yet.