key salt and nonce between c++ and nodejs matches but the weakDecrypt from javascript is not decrypting the data
correctly, whats wrong?

bool weakEncrypt(std::wstring& data)
{
    if (data.empty()) 
        return false;

    unsigned char randomByte;
    RAND_bytes(&randomByte, 1);
    int randomSize = data.length() + 9;
    
    QByteArray password = "test";
    // Generate a salt from the password + a random number
    unsigned char pwhash[SHA256_DIGEST_LENGTH];
    std::vector<unsigned char> saltInput(reinterpret_cast<const unsigned char*>(password.data()), 
                                         reinterpret_cast<const unsigned char*>(password.data()) + password.size());
    saltInput.push_back(static_cast<unsigned char>(randomSize));
    SHA256(saltInput.data(), saltInput.size(), pwhash);
    std::vector<unsigned char> salt(pwhash, pwhash + 32);

    // Derive nonce
    std::vector<unsigned char> context;
    context.reserve(password.size() + salt.size());
    context.insert(context.end(), reinterpret_cast<const unsigned char*>(password.data()), 
                                  reinterpret_cast<const unsigned char*>(password.data()) + password.size());
    context.insert(context.end(), salt.begin(), salt.end());
    
    unsigned char hash[SHA256_DIGEST_LENGTH];
    SHA256(context.data(), context.size(), hash);
    std::vector<unsigned char> nonce(hash, hash + 16);

    // Derive key from password
    unsigned char key[32];
    SHA256(reinterpret_cast<const unsigned char*>(password.data()), password.size(), key);
    
    // Encrypt with derived nonce
    EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new();
    if (!ctx) return false;
    if (EVP_EncryptInit_ex(ctx, EVP_chacha20(), nullptr, key, nonce.data()) != 1)
    {
        EVP_CIPHER_CTX_free(ctx);
        return false;
    }
    
    // Use data string directly for in-place encryption
    size_t originalSize = data.size() * sizeof(wchar_t);
    std::wstring dataCopy = data;
    RtlSecureZeroMemory(&data[0], originalSize);
    data.resize(originalSize / sizeof(wchar_t) + EVP_MAX_BLOCK_LENGTH);
    int ciphertextLen;
    if (EVP_EncryptUpdate(ctx, reinterpret_cast<unsigned char*>(&data[0]), &ciphertextLen, 
                          reinterpret_cast<const unsigned char*>(dataCopy.data()), originalSize) != 1)
    {
        EVP_CIPHER_CTX_free(ctx);
        return false;
    }
    RtlSecureZeroMemory(&dataCopy[0], dataCopy.size() * sizeof(wchar_t));
    int finalLen;
    if (EVP_EncryptFinal_ex(ctx, reinterpret_cast<unsigned char*>(&data[ciphertextLen / sizeof(wchar_t)]), &finalLen) != 1)
    {
        EVP_CIPHER_CTX_free(ctx);
        return false;
    }

    qq << "\nsize:\n"   << randomSize;
	qq << "\nkey\n"     << QByteArray::fromStdString(std::string(reinterpret_cast<char*>(key), sizeof(key))).toBase64();
	qq << "\nsalt\n"    << QByteArray::fromStdString(std::string(reinterpret_cast<char*>(salt.data()), 32)).toBase64();
	qq << "\nnonce\n"   << QByteArray::fromStdString(std::string(reinterpret_cast<char*>(nonce.data()), 16)).toBase64();


    ciphertextLen += finalLen;
    EVP_CIPHER_CTX_free(ctx);
    OPENSSL_cleanse(key, sizeof(key));
    data.resize(ciphertextLen / sizeof(wchar_t));
    return true;
}



function weakDecrypt(data, password = "test")
{

    try
    {
        const length = data.length
        if (!length)
            return decryptionHasFailed[data] = "wd"

        // Generate a salt from the password + random size
        const saltInput  = Buffer.concat([Buffer.from(password), Buffer.from([length + 9])])
        const pwhash     = crypto.createHash('sha256').update(saltInput).digest()
        const salt       = Buffer.from(pwhash)

        const NONCE_SIZE = 16
        const KEY_SIZE   = 32

        // Derive key from password
        const key        = crypto.createHash('sha256').update(password).digest()

        // Derive nonce
        const context   = Buffer.concat([Buffer.from(password), salt])
        const hash      = crypto.createHash('sha256').update(context).digest()
        const nonce     = hash.slice(0, NONCE_SIZE)

        // Log values for comparison
        console.log('\nkey\n',   key.toString('base64'))
        console.log('\salt\n',   salt.toString('base64'))
        console.log('\nnonce\n', nonce.toString('base64'))

        const iv        = nonce //Buffer.concat([nonce, Buffer.from('fdfdfdfd', 'hex')])
        const ctr       = iv.subarray(0, 4).readUInt32LE(0)
        const actNonce  = iv.subarray(4)

        const decipher  = new JSChaCha20(key, actNonce, ctr)
        let decrypted   = decipher.decrypt(Buffer.from(data))
        decrypted       = Buffer.from(decrypted).toString('utf8')
        return decrypted
    } 
    catch (err)
    {
        decryptionHasFailed[data] = "wd"
        return data
    }
}