Sign up
Login
New
Trending
Archive
English
English
Sign up
Login
New Paste
Add Image
<!DOCTYPE html> <html lang="en"> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>TimeSeed.io</title> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/flatpickr/dist/flatpickr.min.css"> <script src="https://cdn.jsdelivr.net/npm/flatpickr"></script> <style> @import url('https://fonts.googleapis.com/css2?family=Poppins:wght@400;500;600;700&family=Inter:wght@700&display=swap'); body { font-family: 'Poppins', sans-serif; background: #2D2D2D; color: #F3F4F6; min-height: 100vh; display: flex; flex-direction: column; align-items: center; justify-content: center; padding: 1.5rem; margin: 0; } .container { background: #374151; border-radius: 0.75rem; width: 100%; max-width: 600px; padding: 2rem; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2); } .title { font-family: 'Poppins', sans-serif; font-size: 3.9rem; font-weight: 700; color: #F3F4F6; text-align: center; position: relative; padding-bottom: 0.75rem; margin-bottom: 1.5rem; line-height: 1; flex-wrap: nowrap; } .title::after { content: ''; position: absolute; bottom: 0; left: 20%; width: 60%; height: 3px; background: #0D9488; border-radius: 2px; } .logo { height: 6rem; width: auto; vertical-align: middle; border-radius: 0.25rem; border: 1px solid #0D9488; box-shadow: 0 0 8px rgba(13, 148, 136, 0.3); transition: transform 0.2s ease, box-shadow 0.2s ease; position: relative; } .logo:hover { transform: scale(1.05); box-shadow: 0 0 12px rgba(13, 148, 136, 0.5); } .logo::after { content: ''; position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: linear-gradient(135deg, rgba(55, 65, 81, 0.1), rgba(13, 148, 136, 0.1)); border-radius: 0.25rem; pointer-events: none; } .tab-list { display: flex; flex-direction: row; gap: 0.5rem; margin-bottom: 1rem; justify-content: center; } .tab-btn { transition: all 0.2s ease; border: none; padding: 0.5rem 1.5rem; font-weight: 600; font-size: 0.875rem; border-radius: 0.5rem; cursor: pointer; position: relative; } #lockit-tab { background: #0D9488; color: #F3F4F6; } #lockit-tab:hover { background: #14B8A6; } #lockit-tab.active { background: #0F766E; box-shadow: 0 0 10px rgba(13, 148, 136, 0.4); border-bottom: 3px solid #F3F4F6; } #lockit-key { font-family: monospace; font-size: 0.75rem; white-space: nowrap; overflow-x: auto; padding: 0.5rem; line-height: 1.2; } .lockit-key-container { display: flex; gap: 0.5rem; width: 100%; align-items: flex-start; } #lockit-generateKey { padding: 0.4rem; font-size: 0.875rem; min-height: 44px; /* WCAG touch target */ } #timeseed-tab { background: #3B82F6; color: #F3F4F6; } #timeseed-tab:hover { background: #60A5FA; } #timeseed-tab.active { background: #2563EB; box-shadow: 0 0 10px rgba(59, 130, 246, 0.4); border-bottom: 3px solid #F3F4F6; } .tab-content { display: none; } .tab-content.active { display: block; } .encode-btn { background: #0D9488; color: #F3F4F6; transition: all 0.2s ease; padding: 0.75rem 1.5rem; border-radius: 0.5rem; font-weight: 500; font-size: 0.875rem; border: none; cursor: pointer; min-height: 44px; /* WCAG touch target */ } .encode-btn:hover { background: #14B8A6; transform: translateY(-1px); } .encode-btn:active { background: #0F766E; } .decode-btn { background: #3B82F6; color: #F3F4F6; transition: all 0.2s ease; padding: 0.75rem 1.5rem; border-radius: 0.5rem; font-weight: 500; font-size: 0.875rem; border: none; cursor: pointer; min-height: 44px; /* WCAG touch target */ } .decode-btn:hover { background: #60A5FA; transform: translateY(-1px); } .decode-btn:active { background: #2563EB; } .encode-btn:disabled, .decode-btn:disabled, .icon-btn:disabled, .copy-btn:disabled { opacity: 0.5; cursor: not-allowed; background: #4B5563; color: #9CA3AF; } .encode-btn:disabled:hover::after, .decode-btn:disabled:hover::after { content: attr(data-tooltip); position: absolute; bottom: 100%; left: 50%; transform: translateX(-50%); background: #374151; color: #F3F4F6; padding: 0.5rem; border-radius: 0.5rem; font-size: 0.75rem; white-space: nowrap; z-index: 10; } .icon-btn { background: #6B7280; color: #F3F4F6; transition: all 0.2s ease; padding: 0.5rem; border-radius: 0.5rem; border: none; cursor: pointer; min-height: 44px; /* WCAG touch target */ } .icon-btn:hover { background: #9CA3AF; transform: scale(1.05); } #timeseed-togglePepperButton { display: flex; align-items: center; justify-content: center; height: 2.75rem; padding: 0; right: 0.5rem; top: 50%; transform: translateY(-50%); position: absolute; } textarea, input { background: #374151; border: 1px solid #6B7280; color: #F3F4F6; transition: all 0.2s ease; border-radius: 0.5rem; padding: 0.75rem; font-size: 0.875rem; width: 100%; box-sizing: border-box; } textarea:focus, input:focus { border-color: #0D9488; box-shadow: 0 0 6px rgba(13, 148, 136, 0.3); outline: none; } textarea.disabled, input.disabled { background: #2D2D2D; border: 1px solid #4B5563; color: #9CA3AF; cursor: not-allowed; } #timeseed-inputCode { font-family: monospace; overflow-x: auto; white-space: nowrap; padding-right: 3rem; } #timeseed-pepperInput { font-family: monospace; } #lockit-log, #timeseed-log { transition: max-height 0.3s ease, opacity 0.3s ease; max-height: 200px; opacity: 1; overflow: auto; background: #374151; border: 1px solid #6B7280; border-radius: 0.5rem; padding: 0.75rem; font-size: 0.75rem; font-family: monospace; display: block; } .log-toggle { background: #0D9488; color: #F3F4F6; padding: 0.5rem 1rem; border-radius: 0.5rem; font-size: 0.875rem; border: none; cursor: pointer; margin-bottom: 0.5rem; } .log-toggle:hover { background: #14B8A6; } .tooltip { position: relative; } .tooltip:hover::after { content: attr(data-tooltip); position: absolute; bottom: 100%; left: 50%; transform: translateX(-50%); background: #374151; color: #F3F4F6; padding: 0.5rem; border-radius: 0.5rem; font-size: 0.75rem; white-space: nowrap; z-index: 10; } .progress-container { width: 100%; background: #374151; border-radius: 0.5rem; margin: 0.75rem 0; overflow: hidden; height: 1rem; } .progress-bar { width: 0%; height: 1rem; background: linear-gradient(45deg, #0D9488, #3B82F6); text-align: center; color: #F3F4F6; font-size: 0.75rem; line-height: 1rem; transition: width 0.3s ease-in-out; /* Smooth progress animation */ } .key-card, .long-term-card { background: #374151; padding: 0.75rem; border-radius: 0.5rem; display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: 0.75rem; border: 1px solid #6B7280; flex-wrap: wrap; gap: 0.5rem; } .key-text { font-family: monospace; font-size: 0.875rem; overflow-wrap: break-word; white-space: normal; flex: 1 1 auto; min-width: 0; margin-right: 0.5rem; color: #F3F4F6; background: #2D2D2D; border: none; padding: 0.5rem; border-radius: 0.25rem; resize: none; height: 2.5rem; line-height: 1.2; } .key-text[readonly] { cursor: text; } .key-text:focus { border-color: #0D9488; box-shadow: 0 0 6px rgba(13, 148, 136, 0.3); } .pass-indicator { font-size: 0.75rem; color: #D1D5DB; margin-right: 0.5rem; align-self: center; } .copy-btn { background: #0D9488; color: #F3F4F6; border: none; padding: 0.5rem; border-radius: 0.5rem; cursor: pointer; font-size: 0.875rem; min-width: 3rem; transition: all 0.2s ease; min-height: 44px; /* WCAG touch target */ } .copy-btn:hover { background: #14B8A6; } .copy-btn:active { background: #0F766E; } .info-table { font-size: 0.875rem; width: 100%; color: #D1D5DB; } .info-table tr td:first-child { font-weight: 500; padding-right: 0.5rem; } .info-table td { padding: 0.25rem 0; } .countdown-timer { font-size: 0.875rem; color: #D1D5DB; display: inline; } .error, .success { font-size: 0.875rem; margin-top: 0.5rem; } .error { color: #F87171; } .success { color: #10B981; } footer { margin-top: 1rem; text-align: center; } footer a { color: #D1D5DB; text-decoration: none; margin: 0 0.5rem; } footer a:hover { color: #14B8A6; } .flex { display: flex; } .flex-col { flex-direction: column; } .gap-4 { gap: 1rem; } .justify-center { justify-content: center; } .relative { position: relative; } .absolute { position: absolute; } .w-full { width: 100%; } .mb-4 { margin-bottom: 1rem; } .mt-2 { margin-top: 0.5rem; } .mt-4 { margin-top: 1rem; } .text-sm { font-size: 0.875rem; } .text-lg { font-size: 1.125rem; } .font-semibold { font-weight: 600; } .font-mono { font-family: monospace; } .resize-none { resize: none; } .hidden { display: none; } #timeseed-inputStatus { position: absolute; right: 0.5rem; top: 50%; transform: translateY(-50%); font-size: 1.125rem; pointer-events: none; z-index: 10; } .lockit-input-mode { display: none; } .lockit-input-mode.active { display: block; } .file-input-wrapper { position: relative; width: 100%; } #lockit-fileInput { position: absolute; top: 0; left: 0; width: 100%; height: 100%; opacity: 0; cursor: pointer; } #lockit-fileButton { background: #0D9488; color: #F3F4F6; border: none; padding: 0.75rem 1.5rem; border-radius: 0.5rem; font-size: 0.875rem; font-weight: 500; font-family: 'Poppins', sans-serif; transition: background 0.2s ease, transform 0.2s ease; cursor: pointer; display: block; min-height: 44px; /* WCAG touch target */ } #lockit-fileButton:hover { background: #14B8A6; transform: translateY(-1px); } #lockit-fileButton:active { background: #0F766E; transform: translateY(0); } #lockit-fileButton.disabled { background: #4B5563; color: #9CA3AF; cursor: not-allowed; transform: none; } #lockit-fileInput.disabled { cursor: not-allowed; } input[type="radio"] { accent-color: #0D9488; transform: scale(0.8); margin-right: 0.5rem; } input[type="radio"]:focus { outline: 2px solid #0D9488; outline-offset: 2px; } .lockit-input-mode-toggle { display: flex; align-items: center; gap: 0.75rem; margin-bottom: 0.5rem; } .input-label { color: #F3F4F6; font-size: 0.875rem; font-weight: 600; } .toggle-switch { position: relative; display: inline-flex; width: 120px; height: 2rem; align-items: center; } .toggle-switch input { position: absolute; width: 100%; height: 100%; opacity: 0; cursor: pointer; z-index: 2; } .slider { position: relative; display: flex; align-items: center; width: 100%; height: 100%; background: #4B5563; /* Pow is Life */ border-radius: 1rem; cursor: pointer; transition: background 0.2s ease; } .slider::before { content: ''; position: absolute; width: 48%; height: 1.6rem; background: #0D9488; /* Teal for text mode */ border-radius: 0.75rem; top: 0.2rem; left: 0.2rem; transition: transform 0.2s ease, background 0.2s ease; } .toggle-switch input:checked + .slider::before { transform: translateX(100%); background: #3B82F6; /* Blue for file mode */ } .toggle-option { flex: 1; text-align: center; color: #D1D5DB; /* Inactive color */ font-size: 0.75rem; font-weight: 500; z-index: 1; transition: color 0.2s ease, font-weight 0.2s ease; } .toggle-switch input:checked + .slider .toggle-file { color: #F3F4F6; font-weight: 700; /* Bold for active */ } .toggle-switch input:not(:checked) + .slider .toggle-text { color: #F3F4F6; font-weight: 700; /* Bold for active */ } @media (max-width: 640px) { .container { padding: 1rem; margin: 0.5rem; } .title { font-size: 2.5rem; } .logo { height: 2.5rem; } .tab-btn { padding: 0.4rem 1rem; font-size: 0.75rem; min-height: 44px; /* WCAG touch target */ } textarea, input { font-size: 0.875rem; padding: 0.5rem; } .encode-btn, .decode-btn, .copy-btn, .icon-btn { padding: 0.5rem; font-size: 0.75rem; min-height: 44px; /* WCAG touch target */ } .lockit-key-container { flex-direction: column; gap: 0.5rem; } #lockit-key { font-size: 0.75rem; padding: 0.5rem; } #lockit-generateKey { padding: 0.5rem; min-height: 44px; } .toggle-switch { width: 100px; height: 1.8rem; } .slider::before { height: 1.4rem; top: 0.2rem; } .toggle-option { font-size: 0.65rem; } .key-card, .long-term-card { flex-direction: column; gap: 0.5rem; } .key-text { font-size: 0.75rem; height: 2rem; } } @media (max-width: 400px) { .container { padding: 0.75rem; } .title { font-size: 2rem; } .toggle-switch { width: 90px; height: 1.6rem; } .slider::before { height: 1.2rem; } .toggle-option { font-size: 0.6rem; } } .lockit-key-input { -webkit-text-security: none !important; text-security: none !important; -webkit-appearance: textfield !important; -moz-appearance: textfield !important; } </style> </head> <body> <div class="container"> <h1 class="title flex items-center gap-4"> <img src="data:image/png;base64,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" alt="Logo" class="logo"> TimeSeed.io </h1> <!-- Tab Navigation --> <div class="tab-list" role="tablist"> <button class="tab-btn active" id="lockit-tab" role="tab" aria-selected="true" aria-controls="lockit-content">🔒 LockIt</button> <button class="tab-btn" id="timeseed-tab" role="tab" aria-selected="false" aria-controls="timeseed-content">🌱 Time-Seed</button> </div> <!-- LockIt Tab Content --> <div id="lockit-content" class="tab-content active" role="tabpanel" aria-labelledby="lockit-tab"> <section aria-labelledby="lockit-password-heading" class="mb-4"> <h2 id="lockit-password-heading" class="text-lg font-semibold mb-2">Set Your Key</h2> <div class="lockit-key-container"> <input type="text" id="lockit-key" class="w-full rounded-lg font-mono lockit-key-input" placeholder="Drop your secret key (max 100 chars)" maxlength="100" autofocus> <button id="lockit-generateKey" class="icon-btn rounded-lg tooltip" data-tooltip="🔄 Generate random Key" aria-label="Generate new key" onclick="lockitGenerateKey()">🔄</button> </div> </section> <section aria-labelledby="lockit-input-heading" class="mb-4"> <h2 id="lockit-input-heading" class="text-lg font-semibold mb-2">Encode or Decode</h2> <div class="lockit-input-mode-toggle mb-2"> <label class="input-label text-sm font-semibold">Input as:</label> <div class="toggle-switch"> <input type="checkbox" id="lockit-input-toggle" aria-label="Switch to file mode" onchange="lockitSwitchMode(this.checked ? 'file' : 'text')"> <span class="slider"> <span class="toggle-option toggle-text">Text</span> <span class="toggle-option toggle-file">File</span> </span> </div> </div> <div id="lockit-text-input" class="lockit-input-mode active"> <textarea id="lockit-inputText" class="w-full p-2 rounded-lg resize-none" placeholder="Type text to encode or paste to decode" rows="4"></textarea> <div id="lockit-inputStatus" class="text-sm text-center mt-2" style="color: rgb(13, 148, 136);" aria-live="polite">Detected: Plain text</div> </div> <div id="lockit-file-input" class="lockit-input-mode hidden"> <div class="file-input-wrapper"> <input type="file" id="lockit-fileInput" class="hidden" aria-label="File input for encoding/decoding" disabled> <button id="lockit-fileButton" class="encode-btn w-full text-left disabled" aria-label="Browse for file" disabled>Choose File</button> </div> <div id="lockit-fileStatus" class="text-sm text-center mt-2" style="color: #D1D5DB;" aria-live="polite">No file selected</div> <div id="lockit-fileProgress" class="progress-container hidden"> <div class="progress-bar" id="lockit-progressBar" aria-live="polite">0%</div> </div> </div> <div class="flex gap-2 mt-2 justify-center"> <button class="encode-btn px-4 py-2 rounded-lg font-semibold" onclick="lockitEncrypt()" aria-label="Encrypt input" data-tooltip="Enter text or select a file to enable">Encode It 🔒</button> <button class="decode-btn px-4 py-2 rounded-lg font-semibold" onclick="lockitDecrypt()" disabled aria-label="Decrypt input" data-tooltip="Enter encoded text or select a .tsx file to enable">Decode It 🔓</button> </div> </section> <section aria-labelledby="lockit-output-heading" class="mb-4"> <h2 id="lockit-output-heading" class="text-lg font-semibold mb-2">Result <span id="lockit-lockIcon">🔓</span></h2> <textarea id="lockit-output" class="w-full p-2 rounded-lg resize-none" readonly rows="4"></textarea> <div class="flex gap-2 mt-2 justify-center"> <button class="icon-btn p-2 rounded-lg tooltip" data-tooltip="📋 Copy Output" aria-label="Copy output" onclick="lockitCopyOutput()">📋</button> <button class="icon-btn p-2 rounded-lg tooltip" data-tooltip="🗑️ Clear Fields" aria-label="Clear fields" onclick="lockitClearFields()">🗑️</button> </div> </section> <div class="mb-4"> <button class="log-toggle" onclick="lockitToggleLog()">Show/Hide Logs</button> <div id="lockit-log" class="mt-2 p-2 rounded-lg text-sm font-mono">[2025-06-21T19:26:00.000Z] LockIt tab loaded</div> </div> </div> <!-- Time-Seed Tab Content --> <div id="timeseed-content" class="tab-content" role="tabpanel" aria-labelledby="timeseed-tab"> <section aria-labelledby="timeseed-heading" class="mb-4"> <h2 id="timeseed-heading" class="text-lg font-semibold mb-2">Time-Seed Key Generator 🌱</h2> <p class="text-sm text-[#D1D5DB] mb-2">⚠️ Store your seed and keys securely and never share them!</p> <div class="mb-4"> <button class="encode-btn px-4 py-2 rounded-lg font-semibold" onclick="timeseedGenerateInputCode()">Generate Seed</button> </div> <div id="timeseed-progressOutput" class="mb-4"></div> <div id="timeseed-entropyOutput" class="mb-4"></div> <div id="timeseed-inputCodeContainer" class="flex flex-col gap-2"> <label for="timeseed-inputCode" class="text-sm font-semibold">Enter TimeSeed</label> <div class="relative flex items-center"> <input type="text" id="timeseed-inputCode" class="w-full p-2 rounded-lg font-mono" placeholder="Paste or enter 50-character seed" maxlength="50" aria-label="50-character seed input" style="padding-right: 3rem;"> <span id="timeseed-inputStatus" class="absolute right-0.5rem top-50% transform -translate-y-50% text-lg"></span> </div> <label for="timeseed-pepperInput" class="text-sm font-semibold">Optional Pepper (Passphrase)</label> <div class="relative flex items-center"> <input type="password" id="timeseed-pepperInput" class="w-full p-2 rounded-lg font-mono" placeholder="Enter a secret passphrase (optional)" aria-label="Optional pepper passphrase"> <button id="timeseed-togglePepperButton" class="icon-btn p-2 rounded-lg tooltip absolute right-2 top-1/2 transform -translate-y-1/2" data-tooltip="👀 Show Pepper" aria-label="Show pepper" onclick="timeseedTogglePepperVisibility()">👁️</button> </div> <p class="text-sm text-[#D1D5DB]">Add a passphrase for extra security. Must match when used.</p> <div class="flex gap-2"> <button class="encode-btn px-4 py-2 rounded-lg font-semibold" id="timeseed-submitSeedButton" onclick="timeseedSubmitSeed()" disabled aria-label="Submit seed">Submit Seed</button> <button class="decode-btn px-4 py-2 rounded-lg font-semibold" onclick="timeseedClearInputs()" aria-label="Clear seed and pepper inputs">Clear</button> </div> </div> <div id="timeseed-output" class="mt-4" data-key="" data-long-term-key=""> <div id="timeseed-keyCards"></div> <div id="timeseed-infoTable"></div> </div> <div class="flex gap-2 mt-2 justify-center"> <button class="icon-btn p-2 rounded-lg tooltip" data-tooltip="📅 Select Past Date" aria-label="Select past date" onclick="timeseedShowDatePicker()">📅</button> <input type="text" id="timeseed-datePicker" class="hidden" placeholder="Select date" aria-label="Select date for past key" readonly> <button class="icon-btn p-2 rounded-lg tooltip" data-tooltip="💾 Download it" aria-label="Download seed and keys" onclick="timeseedDownloadSeed()">💾</button> </div> <div id="timeseed-error" class="error mt-2"></div> </section> <div class="mb-4"> <button class="log-toggle" onclick="timeseedToggleLog()">Show/Hide Logs</button> <div id="timeseed-log" class="mt-2 p-2 rounded-lg text-sm font-mono">[2025-06-21T19:26:00.000Z] Time-Seed tab loaded</div> </div> </div> </div> <footer class="text-sm text-[#D1D5DB] mt-4 text-center"> <a href="https://github.com/avbpodcast/timeseed.io" class="hover:text-[#14B8A6]">Source</a> | <a href="https://timeseed.io/v1/TSexplained.html" class="hover:text-[#14B8A6]">How does it work?</a> | <a href="https://timeseed.io/v1/donate" class="hover:text-[#14B8A6]">Fund Development</a> | <a href="https://timeseed.io/v1/#" onclick="downloadPage()" class="hover:text-[#14B8A6]">Download</a> | <a href="https://timeseed.io/contact.html" class="hover:text-[#14B8A6]">Contact</a> | version 1.14 </footer> <!-- Giving credit to timeseed.io This software is © 2025 @AVB_21 Licensed under the GNU GPL v3. See LICENSE for details. This project (timeseed.io) is licensed under the GNU General Public License v3.0 (GPLv3). You are free to use, copy, modify, and distribute this software, but you must: - Give appropriate credit to the original author ( @AVB_21 / timeseed.io ) - Include this license in all copies or substantial portions of the software. - Distribute modified versions under the same GPLv3 license. There is no warranty for this software. Use at your own risk. Full license: https://www.gnu.org/licenses/gpl-3.0.html --> <script> // Tab Navigation function showTab(tabId) { document.querySelectorAll('.tab-content').forEach(content => content.classList.remove('active')); document.querySelectorAll('.tab-btn').forEach(btn => { btn.classList.remove('active'); btn.setAttribute('aria-selected', 'false'); }); document.getElementById(`${tabId}-content`).classList.add('active'); document.getElementById(`${tabId}-tab`).classList.add('active'); document.getElementById(`${tabId}-tab`).setAttribute('aria-selected', 'true'); } // LockIt JavaScript let lockitLogEntries = []; let lockitHasShownKeyWarning = false; let lockitMode = 'text'; let lockitFileMode = 'standard'; // Tracks file mode (standard or large) function lockitAddLog(message) { const timestamp = new Date().toISOString(); lockitLogEntries.push(`[${timestamp}] ${message}`); lockitUpdateLogDisplay(); } function lockitUpdateLogDisplay() { const logElement = document.getElementById('lockit-log'); if (logElement) logElement.innerText = lockitLogEntries.join('\n'); } function lockitToggleLog() { const logElement = document.getElementById('lockit-log'); logElement.style.display = logElement.style.display === 'none' ? 'block' : 'none'; lockitAddLog(`Log display ${logElement.style.display === 'none' ? 'hidden' : 'shown'}`); } function lockitTruncate(str) { return str && typeof str === 'string' ? (str.length > 3 ? str.slice(0, 3) + '...' : str) : 'null'; } async function lockitComputeHash(str) { const buffer = new TextEncoder().encode(str); const hash = await crypto.subtle.digest('SHA-256', buffer); return Array.from(new Uint8Array(hash)).map(b => b.toString(16).padStart(2, '0')).join(''); } function downloadPage() { const htmlContent = document.documentElement.outerHTML; const blob = new Blob([htmlContent], { type: 'text/html' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = 'lockit-timeseed-v1_13.html'; a.click(); URL.revokeObjectURL(url); lockitAddLog('Page downloaded'); } function lockitGenerateKey() { const words = ['apple', 'bread', 'cloud', 'dream', 'eagle', 'flame', 'grape', 'house', 'boom', 'kaas', 'licht', 'regen', 'ster', 'vogel', 'wind', 'zon', 'quixy', 'zavox', 'loren', 'sythe', 'krill', 'vynix', 'thryme', 'glynt', 'satoshi', 'bitcoin', 'vole', 'war', 'axis', 'river', 'mountain', 'forest', 'stone', 'sand', 'ocean', 'wave', 'shell', 'tree', 'leaf', 'branch', 'root', 'flower', 'seed', 'fruit', 'berry', 'pine', 'oak', 'maple', 'birch', 'willow', 'cedar', 'moss', 'fern', 'grass', 'field', 'meadow', 'hill', 'valley', 'plain', 'desert', 'dune', 'canyon', 'cave', 'cliff', 'peak', 'summit', 'glacier', 'frost', 'snow', 'ice', 'hail', 'storm', 'rain', 'cloudburst', 'mist', 'fog', 'dew', 'sun', 'moon', 'star', 'planet', 'comet', 'meteor', 'galaxy', 'cosmos', 'space', 'rocket', 'orbit', 'satellite', 'probe', 'signal', 'code', 'byte', 'bit', 'hash', 'block', 'chain', 'node', 'wallet', 'miner', 'ledger', 'fork', 'token', 'coin', 'vault', 'key', 'lock', 'safe', 'trust', 'proof', 'stake', 'pool', 'trade', 'market', 'price', 'chart', 'bull', 'bear', 'pump', 'whale', 'shrimp', 'crab', 'fox', 'wolf', 'lynx', 'mole', 'hawk', 'dove', 'crow', 'magpie', 'sparrow', 'robin', 'lark', 'finch', 'wren', 'owl', 'bat']; const maxLength = 48; const getRandomWord = () => words[Math.floor(crypto.getRandomValues(new Uint32Array(1))[0] / (0xffffffff + 1) * words.length)]; const selectedWords = Array.from({ length: 6 }, getRandomWord); const wordPart = selectedWords.join('-'); const wordLength = wordPart.length; const remainingLength = maxLength - wordLength - 1; const getRandomNumber = () => Math.floor(crypto.getRandomValues(new Uint32Array(1))[0] / (0xffffffff + 1) * 9999) + 1; let numbers = Array.from({ length: 4 }, getRandomNumber); let numberPart = numbers.join(''); if (wordLength + 1 + numberPart.length > maxLength) { const maxDigitsPerNumber = Math.floor(remainingLength / 4); if (maxDigitsPerNumber >= 1) { numbers = Array.from({ length: 4 }, () => Math.floor(crypto.getRandomValues(new Uint32Array(1))[0] / (0xffffffff + 1) * Math.pow(10, maxDigitsPerNumber)) + 1); numberPart = numbers.join(''); } else { numberPart = numbers.slice(0, remainingLength).join(''); } } let key = `${wordPart}-${numberPart}`; if (key.length > maxLength) { numberPart = numberPart.slice(0, maxLength - wordLength - 1); key = `${wordPart}-${numberPart}`; } const keyInput = document.getElementById('lockit-key'); if (keyInput) { keyInput.value = key; lockitAddLog(`Generated key: ${lockitTruncate(key)}`); } } function lockitStrToArrayBuffer(str) { return new TextEncoder().encode(str); } function lockitArrayBufferToHex(buffer) { return Array.from(new Uint8Array(buffer)).map(b => b.toString(16).padStart(2, '0')).join(''); } function lockitHexToArrayBuffer(hex) { if (hex.length % 2 !== 0) throw new Error('Invalid hex string'); return new Uint8Array(hex.match(/.{1,2}/g).map(byte => parseInt(byte, 16))).buffer; } async function lockitDeriveKey(password, salt) { const keyMaterial = await crypto.subtle.importKey( 'raw', lockitStrToArrayBuffer(password), 'PBKDF2', false, ['deriveBits', 'deriveKey'] ); return await crypto.subtle.deriveKey( { name: 'PBKDF2', salt: salt, iterations: 1000000, hash: 'SHA-256' }, keyMaterial, { name: 'AES-GCM', length: 256 }, true, ['encrypt', 'decrypt'] ); } async function lockitDeriveKeyCTR(password, salt) { const keyMaterial = await crypto.subtle.importKey( 'raw', lockitStrToArrayBuffer(password), 'PBKDF2', false, ['deriveKey'] ); return crypto.subtle.deriveKey( { name: 'PBKDF2', salt: salt, iterations: 100000, hash: 'SHA-256' }, keyMaterial, { name: 'AES-CTR', length: 256 }, false, ['encrypt', 'decrypt'] ); } function toRadix64(buffer) { lockitAddLog('Starting Radix64 conversion for ' + buffer.byteLength + ' bytes'); const CHUNK_SIZE = 76800; const base64Chunks = []; const uint8 = new Uint8Array(buffer); for (let i = 0; i < uint8.length; i += CHUNK_SIZE) { const chunk = uint8.subarray(i, Math.min(i + CHUNK_SIZE, uint8.length)); const binary = String.fromCharCode(...chunk); try { const base64 = btoa(binary).replace(/[^A-Za-z0-9+/=]/g, ''); base64Chunks.push(base64); lockitAddLog(`Encoded chunk ${i / CHUNK_SIZE + 1} of ${Math.ceil(uint8.length / CHUNK_SIZE)}`); } catch (e) { lockitAddLog(`btoa error in chunk ${i / CHUNK_SIZE + 1}: ${e.message}`); throw new Error(`Failed to encode chunk: ${e.message}`); } } const combinedBase64 = base64Chunks.join(''); if (!/^[A-Za-z0-9+/=]+$/.test(combinedBase64)) { lockitAddLog('Invalid Base64 characters in combined output'); throw new Error('Invalid Base64 output generated'); } const linedBase64 = combinedBase64.match(/.{1,64}/g).join('\n'); lockitAddLog('Completed Radix64 conversion, output length: ' + linedBase64.length); return linedBase64; } function fromRadix64(str) { lockitAddLog('Starting Radix64 decoding, input length: ' + str.length); let base64 = str .replace(/\r\n/g, '\n') .replace(/-----BEGIN TIMESEED CYPHER-----[\s\S]*?Version: TimeSeed v\d+\.\d+\s*\n/, '') .replace(/\n=[\w+/=]{4}\n-----END TIMESEED CYPHER-----\s*$/, '') .replace(/[\n\r\s]+/g, ''); lockitAddLog('Cleaned Base64 string, length: ' + base64.length); lockitAddLog(`Base64 start: ${base64.slice(0, 50)}`); lockitAddLog(`Base64 end: ${base64.slice(-50)}`); const invalidChars = base64.match(/[^A-Za-z0-9+/=]/g); if (invalidChars) { lockitAddLog(`Invalid Base64 characters detected: ${invalidChars.join('').slice(0, 50)}`); throw new Error(`Invalid Base64 string: Contains ${invalidChars.length} non-Base64 characters`); } if (!/^[A-Za-z0-9+/=]+$/.test(base64)) { lockitAddLog('Invalid Base64 characters detected after validation'); throw new Error('Invalid Base64 string: Contains non-Base64 characters'); } if (base64.length % 4 !== 0) { lockitAddLog('Invalid Base64 length: ' + base64.length); throw new Error('Invalid Base64 string: Length must be divisible by 4'); } const CHUNK_SIZE = 76800; const totalBytes = Math.ceil((base64.length / 4) * 3); const byteArray = new Uint8Array(totalBytes); let byteOffset = 0; let chunkCount = Math.ceil(base64.length / CHUNK_SIZE); for (let i = 0; i < base64.length; i += CHUNK_SIZE) { const chunk = base64.slice(i, Math.min(i + CHUNK_SIZE, base64.length)); if (chunk.length % 4 !== 0) { lockitAddLog(`Invalid chunk length at chunk ${i / CHUNK_SIZE + 1}: ${chunk.length}`); throw new Error(`Invalid Base64 chunk length: ${chunk.length}`); } try { const binary = atob(chunk); const chunkBytes = new Uint8Array(binary.length); for (let j = 0; j < binary.length; j++) { chunkBytes[j] = binary.charCodeAt(j); } byteArray.set(chunkBytes, byteOffset); byteOffset += chunkBytes.length; lockitAddLog(`Decoded Base64 chunk ${i / CHUNK_SIZE + 1} of ${chunkCount}, bytes: ${chunkBytes.length}`); } catch (e) { lockitAddLog(`atob error in chunk ${i / CHUNK_SIZE + 1}: ${e.message}`); throw new Error(`Failed to decode Base64 chunk: ${e.message}`); } } const finalArray = byteArray.slice(0, byteOffset); lockitAddLog('Converted to Uint8Array, length: ' + finalArray.length); return finalArray.buffer; } function lockitSetLockIcon(isEncrypted) { const lockIcon = document.getElementById('lockit-lockIcon'); if (lockIcon) { lockIcon.innerHTML = isEncrypted ? '🔒' : '🔓'; lockitAddLog(`Set lock icon to ${isEncrypted ? 'encrypted' : 'decrypted'}`); } else { lockitAddLog('Warning: #lockit-lockIcon element not found'); } } function lockitIsLikelyEncrypted(text) { if (!text || typeof text !== 'string') { return false; } if (text.length < 88 || text.length % 2 !== 0) { return false; } const isHex = /^[0-9a-fA-F]+$/.test(text); lockitAddLog(`Checked text for encryption: ${lockitTruncate(text)}, isHex=${isHex}, length=${text.length}`); return isHex; } function calculateCRC24(data) { const crc24Poly = 0x864CFB; let crc = 0xB704CE; for (const byte of new Uint8Array(data)) { crc ^= byte << 16; for (let i = 0; i < 8; i++) { crc <<= 1; if (crc & 0x1000000) crc ^= crc24Poly; } } crc &= 0xFFFFFF; const bytes = new Uint8Array(3); bytes[0] = (crc >> 16) & 0xFF; bytes[1] = (crc >> 8) & 0xFF; bytes[2] = crc & 0xFF; return toRadix64(bytes).replace(/\n/g, '').padEnd(4, '='); } function createPacket(tag, data) { const dataLength = data.length; const packet = new Uint8Array(5 + dataLength); packet[0] = tag; packet[1] = (dataLength >> 24) & 0xFF; packet[2] = (dataLength >> 16) & 0xFF; packet[3] = (dataLength >> 8) & 0xFF; packet[4] = dataLength & 0xFF; packet.set(data, 5); lockitAddLog(`Created packet: tag=${tag}, length=${dataLength}`); return packet; } async function lockitCheckFileMode(file) { const MAGIC_NUMBER = new TextEncoder().encode('TSLG'); const HEADER_CHECK_SIZE = 4; try { const header = await lockitReadChunk(file, 0, HEADER_CHECK_SIZE); if (header.length >= HEADER_CHECK_SIZE && header[0] === MAGIC_NUMBER[0] && header[1] === MAGIC_NUMBER[1] && header[2] === MAGIC_NUMBER[2] && header[3] === MAGIC_NUMBER[3]) { lockitAddLog(`Detected large mode via header: TSLG`); return 'large'; } const text = await file.slice(0, Math.min(file.size, 100)).text(); if (/^-----BEGIN TIMESEED CYPHER-----/.test(text)) { lockitAddLog(`Detected standard mode via header: BEGIN TIMESEED CYPHER`); return 'standard'; } const mode = file.size > 10 * 1024 * 1024 ? 'large' : 'standard'; lockitAddLog(`No recognizable header, falling back to size-based detection: ${mode} (size: ${file.size} bytes)`); return mode; } catch (e) { lockitAddLog(`Error checking file header: ${e.message}`); const mode = file.size > 10 * 1024 * 1024 ? 'large' : 'standard'; lockitAddLog(`Falling back to size-based detection due to error: ${mode}`); return mode; } } function lockitSwitchMode(mode) { lockitAddLog(`lockitSwitchMode called with mode: ${mode}`); if (mode !== 'text' && mode !== 'file') { lockitAddLog(`Error: Invalid mode ${mode}`); return; } lockitMode = mode; const textInput = document.getElementById('lockit-text-input'); const fileInput = document.getElementById('lockit-file-input'); const fileInputElement = document.getElementById('lockit-fileInput'); const fileButton = document.getElementById('lockit-fileButton'); const inputTextElement = document.getElementById('lockit-inputText'); const toggleInput = document.getElementById('lockit-input-toggle'); if (!textInput || !fileInput || !fileInputElement || !fileButton || !inputTextElement) { lockitAddLog('Error: One or more UI elements missing in lockitSwitchMode'); return; } textInput.classList.toggle('active', mode === 'text'); fileInput.classList.toggle('active', mode === 'file'); fileInputElement.disabled = mode !== 'file'; fileInputElement.classList.toggle('disabled', mode !== 'file'); fileButton.disabled = mode !== 'file'; fileButton.classList.toggle('disabled', mode !== 'file'); inputTextElement.disabled = mode !== 'file'; inputTextElement.classList.toggle('disabled', mode !== 'text'); inputTextElement.value = ''; fileInputElement.value = ''; document.getElementById('lockit-output').value = ''; document.getElementById('lockit-fileStatus').textContent = 'No file selected'; document.getElementById('lockit-fileProgress').classList.add('hidden'); lockitFileMode = 'standard'; toggleInput.setAttribute('aria-label', `Switch to ${mode === 'text' ? 'file' : 'text'} mode`); lockitUpdateUI(); lockitAddLog(`Switched to ${mode} mode successfully`); } async function lockitUpdateUI() { const inputTextElement = document.getElementById('lockit-inputText'); const fileInputElement = document.getElementById('lockit-fileInput'); const statusDiv = document.getElementById(lockitMode === 'text' ? 'lockit-inputStatus' : 'lockit-fileStatus'); const encryptButton = document.querySelector('.encode-btn[onclick="lockitEncrypt()"]'); const decryptButton = document.querySelector('.decode-btn[onclick="lockitDecrypt()"]'); if (!statusDiv || !encryptButton || !decryptButton) { lockitAddLog('Error: UI elements missing'); return; } if (lockitMode === 'text') { const inputText = inputTextElement.value.trim(); const isEncrypted = lockitIsLikelyEncrypted(inputText); encryptButton.disabled = isEncrypted; decryptButton.disabled = !isEncrypted; statusDiv.textContent = isEncrypted ? 'Detected: Encoded text' : 'Detected: Plain text'; statusDiv.style.color = isEncrypted ? '#10B981' : '#0D9488'; } else { const file = fileInputElement.files[0]; if (file) { const isEncrypted = file.name.endsWith('.tsx'); lockitFileMode = await lockitCheckFileMode(file); encryptButton.disabled = isEncrypted; decryptButton.disabled = !isEncrypted; statusDiv.textContent = isEncrypted ? `Detected: Encoded file (${file.name}, ${lockitFileMode === 'large' ? 'Large' : 'Standard'})` : `Detected: Plain file (${file.name}, ${lockitFileMode === 'large' ? 'Large' : 'Standard'})`; statusDiv.style.color = isEncrypted ? '#10B981' : '#0D9488'; lockitAddLog(`File mode set to ${lockitFileMode} for ${file.name}`); } else { encryptButton.disabled = true; decryptButton.disabled = true; statusDiv.textContent = 'No file selected'; statusDiv.style.color = '#D1D5DB'; lockitFileMode = 'standard'; } } } function lockitReadFile(file) { return new Promise((resolve, reject) => { const reader = new FileReader(); reader.onload = () => resolve(reader.result); reader.onerror = () => reject(new Error('Failed to read file')); reader.readAsArrayBuffer(file); }); } async function lockitEncryptFile(file, password) { const progressBar = document.getElementById('lockit-progressBar'); const progressContainer = document.getElementById('lockit-fileProgress'); progressContainer.classList.remove('hidden'); progressBar.style.width = '0%'; progressBar.textContent = '0%'; try { if (lockitFileMode === 'standard' && file.size > 10 * 1024 * 1024) { throw new Error('File exceeds 10 MB limit for Standard mode.'); } lockitAddLog(`Starting encryption for file: ${file.name}, size: ${file.size} bytes, mode: ${lockitFileMode}`); if (lockitFileMode === 'large') { return await lockitEncryptFileCTR(file, password); } const fileData = await lockitReadFile(file); lockitAddLog('File read completed'); progressBar.style.width = '20%'; progressBar.textContent = '20%'; const sessionKey = crypto.getRandomValues(new Uint8Array(32)); const salt = crypto.getRandomValues(new Uint8Array(16)); const iv = crypto.getRandomValues(new Uint8Array(12)); lockitAddLog('Generated session key, salt, and IV'); progressBar.style.width = '40%'; progressBar.textContent = '40%'; const sessionKeyCryptoKey = await crypto.subtle.importKey( 'raw', sessionKey, { name: 'AES-GCM', length: 256 }, false, ['encrypt'] ); const key = await lockitDeriveKey(password, salt); lockitAddLog('Imported session key and derived encryption key'); const sessionKeyIV = crypto.getRandomValues(new Uint8Array(12)); const encryptedSessionKey = await crypto.subtle.encrypt( { name: 'AES-GCM', iv: sessionKeyIV, tagLength: 128 }, key, sessionKey ); lockitAddLog('Encrypted session key'); progressBar.style.width = '60%'; progressBar.textContent = '60%'; const encryptedFile = await crypto.subtle.encrypt( { name: 'AES-GCM', iv: iv, tagLength: 128 }, sessionKeyCryptoKey, fileData ); lockitAddLog('Encrypted file data'); const encryptedArray = new Uint8Array(encryptedFile); const ciphertext = encryptedArray.slice(0, -16); const authTag = encryptedArray.slice(-16); lockitAddLog('Separated ciphertext and auth tag'); progressBar.style.width = '80%'; progressBar.textContent = '80%'; const filename = file.name.split('.').slice(0, -1).join('.').replace(/[^A-Za-z0-9_-]/g, '') || 'file'; const extension = file.name.split('.').pop() || ''; const filenameBuffer = new TextEncoder().encode(filename); const extensionBuffer = new TextEncoder().encode(extension); const filenameLength = new Uint8Array([filenameBuffer.length]); const extensionLength = new Uint8Array([extensionBuffer.length]); lockitAddLog('Prepared filename and extension buffers'); const sessionKeyPacketData = new Uint8Array(16 + 12 + encryptedSessionKey.byteLength); sessionKeyPacketData.set(salt, 0); sessionKeyPacketData.set(sessionKeyIV, 16); sessionKeyPacketData.set(new Uint8Array(encryptedSessionKey), 28); const sessionKeyPacket = createPacket(1, sessionKeyPacketData); const encryptedDataPacketData = new Uint8Array( 1 + filenameBuffer.length + 1 + extensionBuffer.length + 12 + ciphertext.length + 16 ); let offset = 0; encryptedDataPacketData.set(filenameLength, offset); offset += 1; encryptedDataPacketData.set(filenameBuffer, offset); offset += filenameBuffer.length; encryptedDataPacketData.set(extensionLength, offset); offset += 1; encryptedDataPacketData.set(extensionBuffer, offset); offset += extensionBuffer.length; encryptedDataPacketData.set(iv, offset); offset += 12; encryptedDataPacketData.set(ciphertext, offset); offset += ciphertext.length; encryptedDataPacketData.set(authTag, offset); const encryptedDataPacket = createPacket(2, encryptedDataPacketData); lockitAddLog('Created session key and encrypted data packets'); const combinedData = new Uint8Array(sessionKeyPacket.length + encryptedDataPacket.length); combinedData.set(sessionKeyPacket, 0); combinedData.set(encryptedDataPacket, sessionKeyPacket.length); lockitAddLog('Combined packets'); const radix64String = toRadix64(combinedData); const crc24 = calculateCRC24(combinedData); lockitAddLog('Generated Radix64 string and CRC24'); const asciiOutput = `-----BEGIN TIMESEED CYPHER-----\nVersion: TimeSeed v1.13\n\n${radix64String}\n=${crc24}\n-----END TIMESEED CYPHER-----\n`; const blob = new Blob([asciiOutput], { type: 'text/plain' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `${filename}.tsx`; a.click(); URL.revokeObjectURL(url); progressBar.style.width = '100%'; progressBar.textContent = '100%'; lockitAddLog(`Encrypted file: ${filename}.tsx`); return true; } catch (e) { lockitAddLog(`Encode error: ${e.message}, stack: ${e.stack}`); throw e; } finally { setTimeout(() => { progressContainer.classList.add('hidden'); }, 1000); } } async function lockitDecryptFile(file, password) { const progressBar = document.getElementById('lockit-progressBar'); const progressContainer = document.getElementById('lockit-fileProgress'); progressContainer.classList.remove('hidden'); progressBar.style.width = '0%'; progressBar.textContent = '0%'; try { lockitFileMode = await lockitCheckFileMode(file); lockitAddLog(`Starting decryption for file: ${file.name}, size: ${file.size} bytes, mode: ${lockitFileMode}`); if (lockitFileMode === 'large') { return await lockitDecryptFileCTR(file, password); } if (file.size > 15 * 1024 * 1024) { throw new Error('File exceeds 15MB limit for Standard mode.'); } const text = await file.text(); const trimmedText = text.trim(); progressBar.style.width = '20%'; progressBar.textContent = '20%'; const headerRegex = /^-----BEGIN TIMESEED CYPHER-----\s*/m; const footerRegex = /-----END TIMESEED CYPHER-----\s*$/m; if (!headerRegex.test(trimmedText) || !footerRegex.test(trimmedText)) { throw new Error('Invalid TIMESEED file format'); } const lines = trimmedText.split('\n'); const dataLines = []; let crc = null; for (const line of lines) { if (line.startsWith('-----') || line.startsWith('Version:') || line.trim() === '') { continue; } if (line.startsWith('=')) { crc = line.slice(1); continue; } dataLines.push(line); } const radix64Data = dataLines.join(''); if (!crc || !/^[A-Za-z0-9+/=]{4}$/.test(crc)) { throw new Error('Missing or invalid CRC checksum'); } const expectedCRC = crc; const combinedDataBuffer = fromRadix64(radix64Data); const combinedData = new Uint8Array(combinedDataBuffer); progressBar.style.width = '40%'; progressBar.textContent = '40%'; const calculatedCRC = calculateCRC24(combinedDataBuffer); if (calculatedCRC !== expectedCRC) { throw new Error('CRC verification failed'); } let offset = 0; const packets = []; while (offset < combinedData.length) { if (offset + 5 > combinedData.length) { throw new Error('Invalid packet: Incomplete header'); } const tag = combinedData[offset++]; let length = (combinedData[offset++] << 24) | (combinedData[offset++] << 16) | (combinedData[offset++] << 8) | combinedData[offset++]; if (length < 0 || offset + length > combinedData.length) { throw new Error('Invalid packet: Length exceeds data'); } const data = combinedData.slice(offset, offset + length); packets.push({ tag, data }); offset += length; } let sessionKey, salt, filename, extension, ciphertext, iv, authTag, sessionKeyIV; for (const packet of packets) { if (packet.tag === 1) { if (packet.data.length < 28) { throw new Error('Invalid session key packet: Too short'); } salt = packet.data.slice(0, 16); sessionKeyIV = packet.data.slice(16, 28); const encryptedSessionKey = packet.data.slice(28); const key = await lockitDeriveKey(password, salt); progressBar.style.width = '60%'; progressBar.textContent = '60%'; try { sessionKey = await crypto.subtle.decrypt( { name: 'AES-GCM', iv: sessionKeyIV, tagLength: 128 }, key, encryptedSessionKey ); sessionKey = new Uint8Array(sessionKey); } catch (e) { throw new Error('Failed to decrypt session key: Invalid key or corrupted data'); } } else if (packet.tag === 2) { let dataOffset = 0; if (dataOffset >= packet.data.length) { throw new Error('Invalid packet: No data'); } const filenameLength = packet.data[dataOffset++]; if (dataOffset + filenameLength > packet.data.length || filenameLength > 255) { throw new Error('Invalid packet: Filename length exceeds data'); } filename = new TextDecoder().decode(packet.data.slice(dataOffset, dataOffset + filenameLength)); dataOffset += filenameLength; const extensionLength = packet.data[dataOffset++]; if (dataOffset + extensionLength > packet.data.length || extensionLength > 255) { throw new Error('Invalid packet: Extension length exceeds data'); } extension = new TextDecoder().decode(packet.data.slice(dataOffset, dataOffset + extensionLength)); dataOffset += extensionLength; if (dataOffset + 12 > packet.data.length) { throw new Error('Invalid packet: Missing IV'); } iv = packet.data.slice(dataOffset, dataOffset + 12); dataOffset += 12; if (dataOffset + 16 > packet.data.length) { throw new Error('Invalid packet: Missing authentication tag'); } authTag = packet.data.slice(packet.data.length - 16); ciphertext = packet.data.slice(dataOffset, packet.data.length - 16); } } if (!sessionKey || !ciphertext || !iv || !authTag) { throw new Error('Missing required packet data'); } progressBar.style.width = '80%'; progressBar.textContent = '80%'; const sessionKeyCryptoKey = await crypto.subtle.importKey( 'raw', sessionKey, { name: 'AES-GCM', length: 256 }, false, ['decrypt'] ); const encryptedData = new Uint8Array([...ciphertext, ...authTag]); const decryptedData = await crypto.subtle.decrypt( { name: 'AES-GCM', iv: iv, tagLength: 128 }, sessionKeyCryptoKey, encryptedData ); progressBar.style.width = '100%'; progressBar.textContent = '100%'; const blob = new Blob([decryptedData], { type: 'application/octet-stream' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); const outputFilename = extension ? `${filename}.${extension}` : filename; a.href = url; a.download = outputFilename; a.click(); URL.revokeObjectURL(url); lockitAddLog(`Decrypted file: ${outputFilename}`); return true; } catch (e) { lockitAddLog(`Decode error: ${e.message}`); throw e; } finally { setTimeout(() => { progressContainer.classList.add('hidden'); }, 1000); } } async function lockitEncryptFileCTR(file, password) { const CHUNK_SIZE = 1024 * 1024; // 1MB chunks const MAGIC_NUMBER = new TextEncoder().encode('TSLG'); const SALT_SIZE = 16; const IV_SIZE = 16; const EXTENSION_LENGTH_SIZE = 1; const MAX_EXTENSION_LENGTH = 255; const progressBar = document.getElementById('lockit-progressBar'); const progressContainer = document.getElementById('lockit-fileProgress'); progressContainer.classList.remove('hidden'); progressBar.style.width = '0%'; progressBar.textContent = '0%'; try { lockitAddLog(`Starting large file encryption (AES-CTR) for file: ${file.name}, size: ${file.size} bytes`); const salt = crypto.getRandomValues(new Uint8Array(SALT_SIZE)); const iv = crypto.getRandomValues(new Uint8Array(IV_SIZE)); const extension = file.name.split('.').pop() || ''; const extensionBuffer = new TextEncoder().encode(extension.slice(0, MAX_EXTENSION_LENGTH)); const extensionLength = new Uint8Array([extensionBuffer.length]); const key = await lockitDeriveKeyCTR(password, salt); const header = new Uint8Array(MAGIC_NUMBER.length + SALT_SIZE + IV_SIZE + EXTENSION_LENGTH_SIZE + extensionBuffer.length); let offset = 0; header.set(MAGIC_NUMBER, offset); offset += MAGIC_NUMBER.length; header.set(salt, offset); offset += SALT_SIZE; header.set(iv, offset); offset += IV_SIZE; header.set(extensionLength, offset); offset += EXTENSION_LENGTH_SIZE; header.set(extensionBuffer, offset); lockitAddLog(`Created header with magic number TSLG, salt, IV, and extension: ${extension}`); const encryptedChunks = []; offset = 0; const totalSize = file.size; const totalChunks = Math.ceil(totalSize / CHUNK_SIZE); let lastUpdate = 0; const UPDATE_INTERVAL = 100; // Update every 100ms for (let chunkIndex = 0; offset < totalSize; chunkIndex++) { const chunk = await lockitReadChunk(file, offset, CHUNK_SIZE); const encryptedChunk = await lockitEncryptChunkCTR(chunk, key, iv, offset); encryptedChunks.push(encryptedChunk); offset += chunk.length; const now = performance.now(); if (now - lastUpdate >= UPDATE_INTERVAL) { const percentage = (offset / totalSize) * 100; progressBar.style.width = `${Math.min(percentage, 100)}%`; progressBar.textContent = `${Math.round(percentage)}% (${chunkIndex + 1}/${totalChunks})`; await new Promise(resolve => requestAnimationFrame(resolve)); lastUpdate = now; } } // Ensure final progress update progressBar.style.width = '100%'; progressBar.textContent = `100% (${totalChunks}/${totalChunks})`; lockitAddLog('Finalized progress bar for encryption'); const filename = file.name.split('.').slice(0, -1).join('.').replace(/[^A-Za-z0-9_-]/g, '') || 'file'; const encryptedBlob = new Blob([header, ...encryptedChunks]); downloadFile(encryptedBlob, `${filename}.tsx`); lockitAddLog(`Large file encryption completed: ${filename}.tsx`); return true; } catch (e) { lockitAddLog(`Large file encryption error: ${e.message}`); throw e; } finally { setTimeout(() => { progressContainer.classList.add('hidden'); }, 1000); } } async function lockitDecryptFileCTR(file, password) { const CHUNK_SIZE = 1024 * 1024; const MAGIC_NUMBER = new TextEncoder().encode('TSLG'); const MAGIC_NUMBER_SIZE = 4; const SALT_SIZE = 16; const IV_SIZE = 16; const EXTENSION_LENGTH_SIZE = 1; const MAX_EXTENSION_LENGTH = 255; const progressBar = document.getElementById('lockit-progressBar'); const progressContainer = document.getElementById('lockit-fileProgress'); progressContainer.classList.remove('hidden'); progressBar.style.width = '0%'; progressBar.textContent = '0%'; try { lockitAddLog(`Starting large file encryption (AES-CTR) for file: ${file.name}, size: ${file.size} bytes`); const salt = crypto.getRandomValues(new Uint8Array(SALT_SIZE)); const iv = crypto.getRandomValues(new Uint8Array(IV_SIZE)); const extension = file.name.split('.').pop() || ''; const extensionBuffer = new TextEncoder().encode(extension.slice(0, MAX_EXTENSION_LENGTH)); const extensionLength = new Uint8Array([extensionBuffer.length]); const key = await lockitDeriveKeyCTR(password, salt); const header = new Uint8Array(MAGIC_NUMBER.length + SALT_SIZE + IV_SIZE + EXTENSION_LENGTH_SIZE + extensionBuffer.length); let offset = 0; header.set(MAGIC_NUMBER, offset); offset += MAGIC_NUMBER.length; header.set(salt, offset); offset += SALT_SIZE; header.set(iv, offset); offset += IV_SIZE; header.set(extensionLength, offset); offset += EXTENSION_LENGTH_SIZE; header.set(extensionBuffer, offset); lockitAddLog(`Created header with magic number TSLG, salt, IV, and extension: ${extension}`); const encryptedChunks = []; offset = 0; const totalSize = file.size; const totalChunks = Math.ceil(totalSize / CHUNK_SIZE); let lastUpdate = 0; const UPDATE_INTERVAL = 100; // Update every 100ms for (let chunkIndex = 0; offset < totalSize; chunkIndex++) { const chunk = await lockitReadChunk(file, offset, CHUNK_SIZE); const encryptedChunk = await lockitEncryptChunkCTR(chunk, key, iv, offset); encryptedChunks.push(encryptedChunk); offset += chunk.length; const now = performance.now(); if (now - lastUpdate >= UPDATE_INTERVAL) { const percentage = (offset / totalSize) * 100; progressBar.style.width = `${Math.min(percentage, 100)}%`; progressBar.textContent = `${Math.round(percentage)}% (${chunkIndex + 1}/${totalChunks})`; await new Promise(resolve => requestAnimationFrame(resolve)); lastUpdate = now; } } // Ensure final progress update progressBar.style.width = '100%'; progressBar.textContent = `100% (${totalChunks}/${totalChunks})`; lockitAddLog('Finalized progress bar for encryption'); const filename = file.name.split('.').slice(0, -1).join('.').replace(/[^A-Za-z0-9_-]/g, '') || 'file'; const encryptedBlob = new Blob([header, ...encryptedChunks]); downloadFile(encryptedBlob, `${filename}.tsx`); lockitAddLog(`Large file encryption completed: ${filename}.tsx`); return true; } catch (e) { lockitAddLog(`Large file encryption error: ${e.message}`); throw e; } finally { setTimeout(() => { progressContainer.classList.add('hidden'); }, 1000); } } async function lockitDecryptFileCTR(file, password) { const CHUNK_SIZE = 1024 * 1024; const MAGIC_NUMBER = new TextEncoder().encode('TSLG'); const MAGIC_NUMBER_SIZE = 4; const SALT_SIZE = 16; const IV_SIZE = 16; const EXTENSION_LENGTH_SIZE = 1; const MAX_EXTENSION_LENGTH = 255; const progressBar = document.getElementById('lockit-progressBar'); const progressContainer = document.getElementById('lockit-fileProgress'); progressContainer.classList.remove('hidden'); progressBar.style.width = '0%'; progressBar.textContent = '0%'; try { lockitAddLog(`Starting large file decryption (AES-CTR) for file: ${file.name}, size: ${file.size} bytes`); const headerSize = MAGIC_NUMBER_SIZE + SALT_SIZE + IV_SIZE + EXTENSION_LENGTH_SIZE; if (file.size < headerSize) { throw new Error('File too small to contain valid header'); } const header = await lockitReadChunk(file, 0, headerSize); let salt, iv, extensionLength, extensionHeader, extension, headerOffset; if (header[0] === MAGIC_NUMBER[0] && header[1] === MAGIC_NUMBER[1] && header[2] === MAGIC_NUMBER[2] && header[3] === MAGIC_NUMBER[3]) { lockitAddLog('Detected TSLG magic number, processing as large mode'); salt = header.slice(MAGIC_NUMBER_SIZE, MAGIC_NUMBER_SIZE + SALT_SIZE); iv = header.slice(MAGIC_NUMBER_SIZE + SALT_SIZE, MAGIC_NUMBER_SIZE + SALT_SIZE + IV_SIZE); extensionLength = header[MAGIC_NUMBER_SIZE + SALT_SIZE + IV_SIZE]; if (extensionLength > MAX_EXTENSION_LENGTH) { throw new Error('Invalid extension length in header'); } extensionHeader = await lockitReadChunk(file, headerSize, extensionLength); extension = new TextDecoder().decode(extensionHeader); headerOffset = headerSize + extensionLength; } else { lockitAddLog('No TSLG magic number found, attempting legacy large mode detection'); const legacyHeaderSize = SALT_SIZE + IV_SIZE + EXTENSION_LENGTH_SIZE; if (file.size < legacyHeaderSize) { throw new Error('File too small for legacy large mode header'); } const legacyHeader = await lockitReadChunk(file, 0, legacyHeaderSize); salt = legacyHeader.slice(0, SALT_SIZE); iv = legacyHeader.slice(SALT_SIZE, SALT_SIZE + IV_SIZE); extensionLength = legacyHeader[SALT_SIZE + IV_SIZE]; if (extensionLength > MAX_EXTENSION_LENGTH) { throw new Error('Invalid extension length in legacy header'); } extensionHeader = await lockitReadChunk(file, legacyHeaderSize, extensionLength); extension = new TextDecoder().decode(extensionHeader); headerOffset = legacyHeaderSize + extensionLength; } const key = await lockitDeriveKeyCTR(password, salt); const decryptedChunks = []; let offset = headerOffset; const totalSize = file.size - offset; const totalChunks = Math.ceil(totalSize / CHUNK_SIZE); let lastUpdate = 0; const UPDATE_INTERVAL = 100; // Update every 100ms for (let chunkIndex = 0; offset < file.size; chunkIndex++) { const chunk = await lockitReadChunk(file, offset, CHUNK_SIZE); const dataOffset = offset - headerOffset; const decryptedChunk = await lockitDecryptChunkCTR(chunk, key, iv, dataOffset); decryptedChunks.push(decryptedChunk); offset += chunk.length; const now = performance.now(); if (now - lastUpdate >= UPDATE_INTERVAL) { const percentage = ((offset - headerOffset) / totalSize) * 100; progressBar.style.width = `${Math.min(percentage, 100)}%`; progressBar.textContent = `${Math.round(percentage)}% (${chunkIndex + 1}/${totalChunks})`; await new Promise(resolve => requestAnimationFrame(resolve)); lastUpdate = now; } } // Ensure final progress update progressBar.style.width = '100%'; progressBar.textContent = `100% (${totalChunks}/${totalChunks})`; lockitAddLog('Finalized progress bar for decryption'); const filename = file.name.replace('.tsx', '').replace(/[^A-Za-z0-9_-]/g, '') || 'file'; const decryptedBlob = new Blob(decryptedChunks, { type: 'application/octet-stream' }); const outputFilename = extension ? `${filename}.${extension}` : filename; downloadFile(decryptedBlob, outputFilename); lockitAddLog(`Large file decryption completed: ${outputFilename}`); return true; } catch (e) { lockitAddLog(`Large file decryption error: ${e.message}`); throw e; } finally { setTimeout(() => { progressContainer.classList.add('hidden'); }, 1000); } } function lockitReadChunk(file, offset, length) { return new Promise((resolve, reject) => { const reader = new FileReader(); const blob = file.slice(offset, offset + length); reader.onload = () => resolve(new Uint8Array(reader.result)); reader.onerror = reject; reader.readAsArrayBuffer(blob); }); } async function lockitEncryptChunkCTR(chunk, key, iv, dataOffset) { const blockIndex = Math.floor(dataOffset / 16); const counter = lockitGetCounterForBlock(iv, blockIndex); const encrypted = await crypto.subtle.encrypt( { name: 'AES-CTR', counter: counter, length: 64 }, key, chunk ); return new Uint8Array(encrypted); } async function lockitDecryptChunkCTR(chunk, key, iv, dataOffset) { const blockIndex = Math.floor(dataOffset / 16); const counter = lockitGetCounterForBlock(iv, blockIndex); const decrypted = await crypto.subtle.decrypt( { name: 'AES-CTR', counter: counter, length: 64 }, key, chunk ); return new Uint8Array(decrypted); } function lockitGetCounterForBlock(iv, blockIndex) { const ivBigInt = BigInt('0x' + Array.from(iv).map(b => b.toString(16).padStart(2, '0')).join('')); const counterBigInt = ivBigInt + BigInt(blockIndex); const counterBytes = []; for (let i = 0; i < 16; i++) { counterBytes.push(Number((counterBigInt >> BigInt(8 * (15 - i))) & 255n)); } return new Uint8Array(counterBytes); } function downloadFile(blob, filename) { const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = filename; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); } async function lockitEncryptText(plaintext, password) { try { const salt = crypto.getRandomValues(new Uint8Array(16)); const iv = crypto.getRandomValues(new Uint8Array(12)); const key = await lockitDeriveKey(password, salt); const encrypted = await crypto.subtle.encrypt( { name: 'AES-GCM', iv: iv, tagLength: 128 }, key, lockitStrToArrayBuffer(plaintext) ); const encryptedArray = new Uint8Array(encrypted); const ciphertext = encryptedArray.slice(0, -16); const authTag = encryptedArray.slice(-16); const result = lockitArrayBufferToHex(salt) + lockitArrayBufferToHex(iv) + lockitArrayBufferToHex(ciphertext) + lockitArrayBufferToHex(authTag); const passwordHash = await lockitComputeHash(password); const outputHash = await lockitComputeHash(result); lockitAddLog(`Encoded: ${lockitTruncate(result)}, key: ${lockitTruncate(passwordHash)}, output: ${lockitTruncate(outputHash)}, authTag: ${lockitTruncate(lockitArrayBufferToHex(authTag))}`); lockitAddLog('Authentication tag generated successfully'); return result; } catch (e) { lockitAddLog(`Encode error: ${e.message}`); throw e; } } async function lockitDecryptText(ciphertextHex, password) { try { if (ciphertextHex.length < 88) { throw new Error('Invalid text: Too short'); } const salt = lockitHexToArrayBuffer(ciphertextHex.slice(0, 32)); const iv = lockitHexToArrayBuffer(ciphertextHex.slice(32, 56)); const authTag = lockitHexToArrayBuffer(ciphertextHex.slice(-32)); const ciphertext = lockitHexToArrayBuffer(ciphertextHex.slice(56, -32)); const ciphertextWithTag = new Uint8Array([...new Uint8Array(ciphertext), ...new Uint8Array(authTag)]); const key = await lockitDeriveKey(password, salt); const decrypted = await crypto.subtle.decrypt( { name: 'AES-GCM', iv: iv, tagLength: 128 }, key, ciphertextWithTag.buffer ); const plaintext = new TextDecoder().decode(decrypted); const passwordHash = await lockitComputeHash(password); const outputHash = await lockitComputeHash(plaintext); lockitAddLog(`Decoded: ${lockitTruncate(plaintext)}, key: ${lockitTruncate(passwordHash)}, output: ${lockitTruncate(outputHash)}, authTag: ${lockitTruncate(lockitArrayBufferToHex(authTag))}`); lockitAddLog('Authentication tag verified successfully'); return plaintext; } catch (e) { lockitAddLog(`Decode error: ${e.message} (possible invalid authTag or tampered data)`); lockitAddLog('Authentication tag verification failed'); throw e; } } async function lockitEncrypt() { const keyInput = document.getElementById('lockit-key'); const outputElement = document.getElementById('lockit-output'); if (!keyInput.value) { alert('Please enter a key'); lockitAddLog('Encryption attempted without key'); return; } if (keyInput.value.length < 8 && !lockitHasShownKeyWarning) { alert('Weak key: Use 8+ chars for max security! 😎'); lockitHasShownKeyWarning = true; } try { if (lockitMode === 'text') { const inputText = document.getElementById('lockit-inputText').value; if (!inputText) { alert('Please enter text to encode'); lockitAddLog('Encryption attempted without text'); return; } const result = await lockitEncryptText(inputText, keyInput.value); document.getElementById('lockit-inputText').value = result; outputElement.value = result; try { lockitSetLockIcon(true); lockitUpdateUI(); } catch (e) { lockitAddLog(`UI update error: ${e.message}`); } } else { const fileInput = document.getElementById('lockit-fileInput'); const file = fileInput.files[0]; if (!file) { alert('Please select a file to encode'); lockitAddLog('Encryption attempted without file'); return; } await lockitEncryptFile(file, keyInput.value); outputElement.value = ''; } } catch (e) { alert(`Encryption failed: ${e.message}`); lockitAddLog(`Encode error: ${e.message}`); } } async function lockitDecrypt() { const keyInput = document.getElementById('lockit-key'); const outputElement = document.getElementById('lockit-output'); if (!keyInput.value) { alert('Please enter a key'); lockitAddLog('Decryption attempted without key'); return; } if (keyInput.value.length < 8 && !lockitHasShownKeyWarning) { alert('Weak key: Use 8+ chars for max security!'); lockitHasShownKeyWarning = true; } try { if (lockitMode === 'text') { const inputText = document.getElementById('lockit-inputText').value; if (!inputText) { alert('Please enter encoded text to decode'); lockitAddLog('Decryption attempted without text'); return; } const result = await lockitDecryptText(inputText, keyInput.value); outputElement.value = result; lockitSetLockIcon(false); lockitUpdateUI(); } else { const fileInput = document.getElementById('lockit-fileInput'); if (!fileInput.files[0]) { alert('Please select a file to decode'); lockitAddLog('Decryption attempted without file'); return; } await lockitDecryptFile(fileInput.files[0], keyInput.value); outputElement.value = ''; } } catch (e) { alert(`Error: ${e.message}`); lockitAddLog(`Decode error: ${e.message}`); } } function lockitCopyOutput() { const outputElement = document.getElementById('lockit-output'); if (outputElement.value) { navigator.clipboard.writeText(outputElement.value).then(() => { alert('Output copied! 📋'); lockitAddLog('Copied to clipboard'); }).catch(e => { lockitAddLog(`Copy error: ${e.message}`); }); } else { lockitAddLog('Copy attempted with empty output'); } } function lockitClearFields() { document.getElementById('lockit-key').value = ''; document.getElementById('lockit-inputText').value = ''; document.getElementById('lockit-fileInput').value = ''; document.getElementById('lockit-output').value = ''; document.getElementById('lockit-fileStatus').textContent = 'No file selected'; document.getElementById('lockit-fileProgress').classList.add('hidden'); lockitFileMode = 'standard'; lockitSetLockIcon(false); lockitUpdateUI(); lockitAddLog('Cleared fields'); } function lockitInit() { lockitAddLog('LockIt tab loaded'); const inputTextElement = document.getElementById('lockit-inputText'); const fileInputElement = document.getElementById('lockit-fileInput'); const fileButton = document.getElementById('lockit-fileButton'); const keyInput = document.getElementById('lockit-key'); const toggleInput = document.getElementById('lockit-input-toggle'); if (keyInput) { console.log('[DEBUG] Initialized #lockit-key with type=text'); } else { console.error('[ERROR] #lockit-key not found during init'); } if (toggleInput) { toggleInput.checked = lockitMode === 'file'; toggleInput.addEventListener('change', () => { const newMode = toggleInput.checked ? 'file' : 'text'; lockitAddLog(`Toggle clicked, switching to mode: ${newMode}`); lockitSwitchMode(newMode); }); } else { console.error('[ERROR] #lockit-input-toggle not found during init'); lockitAddLog('Error: Toggle input not found'); } if (inputTextElement) { inputTextElement.addEventListener('input', lockitUpdateUI); inputTextElement.addEventListener('paste', function(e) { setTimeout(() => { this.value = this.value.trim(); lockitUpdateUI(); }, 0); }); } if (fileInputElement) { fileInputElement.addEventListener('change', () => { const fileStatus = document.getElementById('lockit-fileStatus'); if (fileInputElement.files && fileInputElement.files[0]) { fileStatus.textContent = `Selected: ${fileInputElement.files[0].name}`; lockitAddLog(`File selected: ${fileInputElement.files[0].name}`); } else { fileStatus.textContent = 'No file selected'; lockitAddLog('File selection cleared'); } lockitUpdateUI(); }); } if (fileButton && fileInputElement) { fileButton.addEventListener('click', (e) => { if (!fileInputElement.disabled) { fileInputElement.click(); lockitAddLog('File browse button clicked'); } e.preventDefault(); }); } lockitUpdateUI(); } // Time-Seed JavaScript let timeseedLogEntries = []; let timeseedIsSeedSubmitted = false; let timeseedCalibratedIterations = 135000; let timeseedUpdateCountdownIntervalId = null; let timeseedLastLoggedKeys = { days: '', terms: '' }; function timeseedAddLog(message) { const timestamp = new Date().toISOString(); timeseedLogEntries.push(`[${timestamp}] ${message}`); timeseedUpdateLogDisplay(); } function timeseedToggleLog() { const logElement = document.getElementById('timeseed-log'); logElement.style.display = logElement.style.display === 'none' ? 'block' : 'none'; timeseedAddLog(`Log display ${logElement.style.display === 'none' ? '' : 'shown'}`); } function timeseedUpdateLogDisplay() { const logElement = document.getElementById('timeseed-log'); if (logElement) logElement.innerText = timeseedLogEntries.join('\n'); } function timeseedTruncate(str) { return str && typeof str === 'string' ? (str.length > 10 ? str.slice(0, 10) + '...' : str) : 'null'; } async function timeseedSha256(message) { const msgBuffer = new TextEncoder().encode(message); const hashBuffer = await crypto.subtle.digest('SHA-256', msgBuffer); return Array.from(new Uint8Array(hashBuffer)).map(b => b.toString(36).padStart(2, '0')).join(''); } async function timeseedCalibrateIterations() { const testIterations = 1000; const targetTime = 2000; const maxIterations = 10000000; const startTime = performance.now(); let hash = '0'; for (let i = 0; i < testIterations;) { hash = await timeseedSha256(hash + i); i++; } const elapsed = performance.now() - startTime; let iterationsPerMs = testIterations / elapsed; if (!isFinite(iterationsPerMs) || iterationsPerMs <= 0) { iterationsPerMs = 1000; } let calibratedIterations = Math.floor(iterationsPerMs * targetTime); if (calibratedIterations > maxIterations) { calibratedIterations = maxIterations; } timeseedCalibratedIterations = calibratedIterations; timeseedAddLog(`Calibrated ${timeseedCalibratedIterations} iterations for ~${targetTime}ms`); return timeseedCalibratedIterations; } function timeseedTogglePepperVisibility() { const pepperInput = document.getElementById('timeseed-pepperInput'); const toggleButton = document.getElementById('timeseed-togglePepperButton'); if (pepperInput && toggleButton) { if (pepperInput.type === 'password') { pepperInput.type = 'text'; toggleButton.setAttribute('data-tooltip', 'Hide'); toggleButton.innerHTML = '🙈'; toggleButton.setAttribute('aria-label', 'Hide pepper'); } else { pepperInput.type = 'password'; toggleButton.setAttribute('data-tooltip', 'Show'); toggleButton.innerHTML = '👁️'; toggleButton.setAttribute('aria-label', 'Show pepper'); } timeseedAddLog('Toggled pepper visibility'); } } function timeseedClearSession(fullReset = true) { const entropyOutput = document.getElementById('timeseed-entropyOutput'); const outputDiv = document.getElementById('timeseed-output'); const inputCodeField = document.getElementById('timeseed-inputCode'); const pepperInput = document.getElementById('timeseed-pepperInput'); const errorDiv = document.getElementById('timeseed-error'); const datePicker = document.getElementById('timeseed-datePicker'); const submitSeedButton = document.getElementById('timeseed-submitSeedButton'); if (fullReset) { if (entropyOutput) entropyOutput.innerHTML = ''; if (outputDiv) { outputDiv.innerHTML = ` <div id="timeseed-keyCards"></div> <div id="timeseed-infoTable"></div> `; outputDiv.dataset.key = ''; outputDiv.dataset.longTermKey = ''; } } if (inputCodeField) { inputCodeField.value = ''; inputCodeField.classList.remove('valid', 'invalid'); document.getElementById('timeseed-inputStatus').textContent = ''; } if (pepperInput) { pepperInput.value = ''; pepperInput.type = 'password'; document.getElementById('timeseed-togglePepperButton').innerHTML = '👁️'; document.getElementById('timeseed-togglePepperButton').setAttribute('data-tooltip', 'Show'); document.getElementById('timeseed-togglePepperButton').setAttribute('aria-label', 'Show pepper'); } if (errorDiv) errorDiv.innerText = ''; if (datePicker && datePicker._flatpickr) datePicker._flatpickr.clear(); if (submitSeedButton) submitSeedButton.disabled = true; timeseedIsSeedSubmitted = false; if (timeseedUpdateCountdownIntervalId) { clearInterval(timeseedUpdateCountdownIntervalId); timeseedUpdateCountdownIntervalId = null; } timeseedLastLoggedKeys = { days: '', terms: '' }; } function timeseedClearInputs() { timeseedClearSession(false); timeseedAddLog('Cleared inputs'); } async function timeseedGenerateInputCode() { const entropyOutput = document.getElementById('timeseed-entropyOutput'); const progressOutput = document.getElementById('timeseed-progressOutput'); const inputCodeField = document.getElementById('timeseed-inputCode'); const pepperInput = document.getElementById('timeseed-pepperInput'); const errorDiv = document.getElementById('timeseed-error'); const submitSeedButton = document.getElementById('timeseed-submitSeedButton'); if (!entropyOutput || !progressOutput || !inputCodeField || !pepperInput || !errorDiv || !submitSeedButton) { errorDiv.innerText = 'UI error: Missing elements. Please refresh.'; timeseedAddLog('Error: Missing DOM elements'); return; } timeseedClearSession(true); progressOutput.innerHTML = ` <div>Generating secure seed...</div> <div class="progress-container"> <div class="progress-bar" id="timeseed-seedProgressBar">0%</div> </div> `; const seedProgressBar = document.getElementById('timeseed-seedProgressBar'); timeseedAddLog('Starting seed generation'); try { let progress = 0; const updateProgress = () => { progress += 10; if (progress <= 100) { seedProgressBar.style.width = `${progress}%`; seedProgressBar.innerText = `${progress}%`; } }; const alphabet = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; let seed = ''; for (let i = 0; i < 50; i++) { const randomIndex = Math.floor(crypto.getRandomValues(new Uint32Array(1))[0] / (0xffffffff + 1) * alphabet.length); seed += alphabet[randomIndex]; if (i % 5 === 4) { updateProgress(); await new Promise(resolve => setTimeout(resolve, 100)); } } inputCodeField.value = seed; inputCodeField.classList.add('valid'); document.getElementById('timeseed-inputStatus').textContent = '✅'; submitSeedButton.disabled = false; entropyOutput.innerHTML = ` <p class="text-sm">Generated Seed: <span class="font-mono">${seed}</span></p> <p class="text-sm text-[#D1D5DB]">Copy and store this seed securely!</p> `; seedProgressBar.style.width = '100%'; seedProgressBar.innerText = '100%'; await new Promise(resolve => setTimeout(resolve, 500)); progressOutput.innerHTML = ''; timeseedAddLog(`Seed generated: ${timeseedTruncate(seed)}`); } catch (err) { errorDiv.innerText = 'Failed to generate seed. Please try again.'; timeseedAddLog(`Seed generation error: ${err.message}`); progressOutput.innerHTML = ''; } } async function timeseedDeriveAESKey(seed, date, pepper = '') { try { if (!window.crypto || !window.crypto.subtle) { throw new Error('Web Crypto API not supported'); } const seedBuffer = new TextEncoder().encode(seed); const key = await crypto.subtle.importKey( 'raw', seedBuffer, { name: 'HKDF' }, false, ['deriveBits'] ); const saltInput = `${seed}:${date}`; const saltBuffer = new TextEncoder().encode(saltInput); const saltHash = await crypto.subtle.digest('SHA-256', saltBuffer); const salt = new Uint8Array(saltHash).slice(0, 16); const infoString = pepper ? `${date}:${pepper}` : date; const derivedBits = await crypto.subtle.deriveBits( { name: 'HKDF', hash: 'SHA-256', salt: salt, info: new TextEncoder().encode(infoString) }, key, 256 ); const keyArray = new Uint8Array(derivedBits); const keyHex = Array.from(keyArray).map(b => b.toString(16).padStart(2, '0')).join(''); const saltHex = Array.from(salt).map(b => b.toString(16).padStart(2, '0')).join(''); timeseedAddLog(`Derived key for seed=${timeseedTruncate(seed)}, date=${date}, pepper=${timeseedTruncate(pepper)}, salt=${timeseedTruncate(saltHex)}: ${timeseedTruncate(keyHex)}`); return keyHex; } catch (err) { timeseedAddLog(`Key derivation error: ${err.message}`); throw err; } } function timeseedGetTodayUTC() { const now = new Date(); return `${now.getUTCFullYear()}-${String(now.getUTCMonth() + 1).padStart(2, '0')}-${String(now.getUTCDate()).padStart(2, '0')}`; } function timeseedGetLongTermDate() { const now = new Date(); const currentYear = now.getUTCFullYear(); const jul1 = new Date(Date.UTC(currentYear, 6, 1)); if (now < jul1) { return `${currentYear}-01-01`; } return `${currentYear}-07-01`; } function timeseedGetLongTermDateForDate(selectedDate) { const date = new Date(selectedDate); const currentYear = date.getUTCFullYear(); const jul1 = new Date(Date.UTC(currentYear, 6, 1)); if (date < jul1) { return `${currentYear}-01-01`; } return `${currentYear}-07-01`; } function timeseedUpdateCountdown() { const now = new Date(); const midnightUTC = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate() + 1)); const timeLeftMs = midnightUTC - now; const hours = Math.floor(timeLeftMs / (1000 * 60 * 60)); const minutes = Math.floor((timeLeftMs % (1000 * 60 * 60)) / (1000 * 60)); const seconds = Math.floor((timeLeftMs % (1000 * 60)) / 1000); const countdownElement = document.querySelector('.countdown-timer'); if (countdownElement) { countdownElement.innerText = `New key in ${hours}h ${minutes}m ${seconds}s`; } if (timeLeftMs <= 0 && timeseedIsSeedSubmitted) { timeseedSubmitSeed(); } } async function timeseedCopyKey(event, key, label) { try { await navigator.clipboard.writeText(key); timeseedAddLog(`${label} copied`); event.target.innerText = '✅ Copied!'; setTimeout(() => { event.target.innerText = '📋 Copy'; }, 2000); } catch (err) { timeseedAddLog(`Copy error: ${err.message}`); } } function timeseedUseInLockIt(key, label) { const lockitKeyInput = document.getElementById('lockit-key'); if (lockitKeyInput) { lockitKeyInput.value = key; showTab('lockit'); timeseedAddLog(`Used ${label} in LockIt: ${timeseedTruncate(key)}`); } else { timeseedAddLog(`Error: LockIt input not found for ${label}`); } } function timeseedValidateInputCode(code) { const isValid = /^[a-zA-Z0-9]{50}$/.test(code); const inputCodeField = document.getElementById('timeseed-inputCode'); const inputStatus = document.getElementById('timeseed-inputStatus'); const submitSeedButton = document.getElementById('timeseed-submitSeedButton'); if (inputCodeField && inputStatus && submitSeedButton) { inputCodeField.classList.remove('valid', 'invalid'); if (code.length === 0) { inputStatus.textContent = ''; submitSeedButton.disabled = true; } else if (isValid) { inputCodeField.classList.add('valid'); inputStatus.textContent = '✅'; submitSeedButton.disabled = false; } else { inputCodeField.classList.add('invalid'); inputStatus.textContent = '❌'; submitSeedButton.disabled = true; } } return isValid; } async function timeseedSubmitSeed(selectedDate = null) { const inputCodeField = document.getElementById('timeseed-inputCode'); const pepperInput = document.getElementById('timeseed-pepperInput'); const errorDiv = document.getElementById('timeseed-error'); const outputDiv = document.getElementById('timeseed-output'); const keyCards = document.getElementById('timeseed-keyCards'); const infoTable = document.getElementById('timeseed-infoTable'); if (!inputCodeField || !pepperInput || !errorDiv || !outputDiv || !keyCards || !infoTable) { errorDiv.innerText = 'UI error: Missing elements. Please refresh.'; timeseedAddLog('Error: Missing DOM elements'); return; } const seed = inputCodeField.value.trim(); const pepper = pepperInput.value.trim(); if (!timeseedValidateInputCode(seed)) { errorDiv.innerText = 'Invalid seed: Must be 50 alphanumeric characters.'; timeseedAddLog(`Invalid seed: ${timeseedTruncate(seed)}`); return; } errorDiv.innerText = ''; timeseedIsSeedSubmitted = true; try { const date = selectedDate || timeseedGetTodayUTC(); const dailyKey = await timeseedDeriveAESKey(seed, date, pepper); const longTermDate = selectedDate ? timeseedGetLongTermDateForDate(selectedDate) : timeseedGetLongTermDate(); const longTermKey = await timeseedDeriveAESKey(seed, longTermDate, pepper); if (dailyKey !== timeseedLastLoggedKeys.days || longTermKey !== timeseedLastLoggedKeys.terms) { timeseedAddLog(`Generated keys: Daily=${timeseedTruncate(dailyKey)}, Long-term=${timeseedTruncate(longTermKey)} for date=${date}`); timeseedLastLoggedKeys = { days: dailyKey, terms: longTermKey }; } outputDiv.dataset.key = dailyKey; outputDiv.dataset.longTermKey = longTermKey; const dailyKeyCard = ` <div class="key-card"> <span class="pass-indicator">Daily Key (${date}):</span> <textarea class="key-text" readonly>${dailyKey}</textarea> <button class="copy-btn" onclick="timeseedCopyKey(event, '${dailyKey}', 'Daily Key')">📋 Copy</button> <button class="copy-btn" onclick="timeseedUseInLockIt('${dailyKey}', 'Daily Key')">🔒 Use in LockIt</button> </div> `; const longTermKeyCard = ` <div class="long-term-card"> <span class="pass-indicator">Long-term Key (${longTermDate}):</span> <textarea class="key-text" readonly>${longTermKey}</textarea> <button class="copy-btn" onclick="timeseedCopyKey(event, '${longTermKey}', 'Long-term Key')">📋 Copy</button> <button class="copy-btn" onclick="timeseedUseInLockIt('${longTermKey}', 'Long-term Key')">🔒 Use in LockIt</button> </div> `; keyCards.innerHTML = dailyKeyCard + longTermKeyCard; const seedHash = await timeseedSha256(seed); const pepperHash = pepper ? await timeseedSha256(pepper) : 'None'; const isToday = date === timeseedGetTodayUTC(); infoTable.innerHTML = ` <table class="info-table"> <tr><td>Seed Hash:</td><td class="font-mono">${seedHash}</td></tr> <tr><td>Pepper Hash:</td><td class="font-mono">${pepperHash}</td></tr> <tr><td>Key Date:</td><td>${date}</td></tr> <tr><td>Countdown:</td><td><span class="countdown-timer">${isToday ? '' : 'N/A for selected date'}</span></td></tr> </table> `; if (isToday) { if (timeseedUpdateCountdownIntervalId) { clearInterval(timeseedUpdateCountdownIntervalId); } timeseedUpdateCountdown(); timeseedUpdateCountdownIntervalId = setInterval(timeseedUpdateCountdown, 1000); } else { if (timeseedUpdateCountdownIntervalId) { clearInterval(timeseedUpdateCountdownIntervalId); timeseedUpdateCountdownIntervalId = null; } const countdownElement = document.querySelector('.countdown-timer'); if (countdownElement) countdownElement.innerText = 'N/A for selected date'; } timeseedAddLog(`Seed submitted: ${timeseedTruncate(seed)} for date=${date}`); } catch (err) { errorDiv.innerText = `Error: ${err.message}`; timeseedAddLog(`Key error: ${err.message}`); } } function timeseedShowDatePicker() { const datePicker = document.getElementById('timeseed-datePicker'); if (!datePicker._flatpickr) { flatpickr(datePicker, { maxDate: new Date().setFullYear(new Date().getFullYear() + 2), dateFormat: 'Y-m-d', onChange: async (selectedDates, dateStr) => { if (selectedDates.length > 0) { const selectedDate = dateStr; timeseedAddLog(`Selected date: ${selectedDate}`); await timeseedSubmitSeed(selectedDate); datePicker._flatpickr.close(); } }, }); } datePicker._flatpickr.open(); } async function timeseedDownloadSeed() { const inputCodeField = document.getElementById('timeseed-inputCode'); const pepperInput = document.getElementById('timeseed-pepperInput'); const outputDiv = document.getElementById('timeseed-output'); const errorDiv = document.getElementById('timeseed-error'); if (!inputCodeField || !pepperInput || !outputDiv || !errorDiv) { errorDiv.innerText = 'UI error: Missing elements. Please refresh.'; timeseedAddLog('Error: Missing DOM elements'); return; } const seed = inputCodeField.value.trim(); const pepper = pepperInput.value.trim(); const dailyKey = outputDiv.dataset.key || 'Not generated'; const longTermKey = outputDiv.dataset.longTermKey || 'Not generated'; if (!seed) { errorDiv.innerText = 'No seed to download.'; timeseedAddLog('Download attempted without seed'); return; } try { const content = `Time-Seed Backup (v1.13) - ${new Date().toISOString()} ---------------------------------------- Seed: ${seed} Pepper: ${pepper || 'None'} Daily Key (${timeseedGetTodayUTC()}): ${dailyKey} Long-term Key (${timeseedGetLongTermDate()}): ${longTermKey} ---------------------------------------- Generated by LockIt & Time-Seed Store securely and do not share! `; const blob = new Blob([content], { type: 'text/plain' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `timeseed-backup-${timeseedGetTodayUTC()}.txt`; a.click(); URL.revokeObjectURL(url); timeseedAddLog('Downloaded seed and keys'); } catch (err) { errorDiv.innerText = `Error: ${err.message}`; timeseedAddLog(`Download error: ${err.message}`); } } function timeseedInit() { timeseedAddLog('Time-Seed tab loaded'); const inputCodeField = document.getElementById('timeseed-inputCode'); const submitSeedButton = document.getElementById('timeseed-submitSeedButton'); if (inputCodeField && submitSeedButton) { inputCodeField.addEventListener('input', () => { const code = inputCodeField.value.trim(); timeseedValidateInputCode(code); }); inputCodeField.addEventListener('paste', () => { setTimeout(() => { const code = inputCodeField.value.trim(); timeseedValidateInputCode(code); }, 0); }); } timeseedCalibrateIterations(); } // Initialize Tabs and Event Listeners document.getElementById('lockit-tab').addEventListener('click', () => showTab('lockit')); document.getElementById('timeseed-tab').addEventListener('click', () => showTab('timeseed')); document.addEventListener('DOMContentLoaded', () => { lockitInit(); timeseedInit(); showTab('lockit'); }); </script> </body> </html>
Settings
Title :
[Optional]
Paste Folder :
[Optional]
Select
Syntax :
[Optional]
Select
Markup
CSS
JavaScript
Bash
C
C#
C++
Java
JSON
Lua
Plaintext
C-like
ABAP
ActionScript
Ada
Apache Configuration
APL
AppleScript
Arduino
ARFF
AsciiDoc
6502 Assembly
ASP.NET (C#)
AutoHotKey
AutoIt
Basic
Batch
Bison
Brainfuck
Bro
CoffeeScript
Clojure
Crystal
Content-Security-Policy
CSS Extras
D
Dart
Diff
Django/Jinja2
Docker
Eiffel
Elixir
Elm
ERB
Erlang
F#
Flow
Fortran
GEDCOM
Gherkin
Git
GLSL
GameMaker Language
Go
GraphQL
Groovy
Haml
Handlebars
Haskell
Haxe
HTTP
HTTP Public-Key-Pins
HTTP Strict-Transport-Security
IchigoJam
Icon
Inform 7
INI
IO
J
Jolie
Julia
Keyman
Kotlin
LaTeX
Less
Liquid
Lisp
LiveScript
LOLCODE
Makefile
Markdown
Markup templating
MATLAB
MEL
Mizar
Monkey
N4JS
NASM
nginx
Nim
Nix
NSIS
Objective-C
OCaml
OpenCL
Oz
PARI/GP
Parser
Pascal
Perl
PHP
PHP Extras
PL/SQL
PowerShell
Processing
Prolog
.properties
Protocol Buffers
Pug
Puppet
Pure
Python
Q (kdb+ database)
Qore
R
React JSX
React TSX
Ren'py
Reason
reST (reStructuredText)
Rip
Roboconf
Ruby
Rust
SAS
Sass (Sass)
Sass (Scss)
Scala
Scheme
Smalltalk
Smarty
SQL
Soy (Closure Template)
Stylus
Swift
TAP
Tcl
Textile
Template Toolkit 2
Twig
TypeScript
VB.Net
Velocity
Verilog
VHDL
vim
Visual Basic
WebAssembly
Wiki markup
Xeora
Xojo (REALbasic)
XQuery
YAML
HTML
Expiration :
[Optional]
Never
Self Destroy
10 Minutes
1 Hour
1 Day
1 Week
2 Weeks
1 Month
6 Months
1 Year
Status :
[Optional]
Public
Unlisted
Private (members only)
Password :
[Optional]
Description:
[Optional]
Tags:
[Optional]
Encrypt Paste
(
?
)
Create Paste
You are currently not logged in, this means you can not edit or delete anything you paste.
Sign Up
or
Login
Site Languages
×
English