import { createTopBar } from '../components/topbar.js'; import { state } from '../state.js'; export const pageMeta = { id: 'registration-faq-view', title: 'Вопросы о регистрации', showAppChrome: false }; export const REGISTRATION_FAQ_TOPICS = [ { id: 'keys-storage', shortTitle: 'Где ключи', title: 'У кого хранятся ключи?', paragraphs: [ 'Рабочие приватные ключи хранятся только у вас: на вашем устройстве или на доверенных устройствах, которые вы контролируете сами.', 'Ключ доступа и ключ блокчейна сохраняются на авторизованном устройстве в зашифрованном контейнере. Приватный ключ восстановления на устройстве не сохраняется: он вычисляется из пароля только на время операций, где действительно нужен.', 'Сияние не хранит ваши приватные ключи на сервере. В будущем для повышенной защиты рабочие ключи можно будет вынести на отдельное программируемое устройство с открытым кодом, например ESP32-контроллер.', ], }, { id: 'reliability', shortTitle: 'Надёжность', title: 'Насколько это надёжно?', paragraphs: [ 'Мы делаем ставку на открытость: клиентский код открыт, серверный код открыт, протокол открыт. Это позволяет проверять систему независимо, а не верить обещаниям на слово.', 'Мы рекомендуем использовать браузеры с открытым исходным кодом. Позже планируются отдельные приложения для Android, iPhone, Ubuntu Touch и Linux, тоже с открытым кодом.', 'Проект распространяется по лицензии AGPL v3. Часть важных данных и регистрационных записей также опирается на блокчейн-слой, чтобы уменьшать зависимость от одной закрытой стороны.', ], }, { id: 'key-derivation', shortTitle: 'Как пароль превращается в ключи', title: 'Как генерируются ключи и что делает пароль?', paragraphs: [ 'Из вашего логина и пароля с помощью Argon2id вычисляется специальный секрет.', 'Из этого секрета детерминированно строятся три ключа: ключ восстановления (Recovery), ключ блокчейна и ключ доступа.', 'Это значит, что логин и пароль не просто проверяются на сервере, а реально участвуют в создании ваших ключей. У разных логинов даже с одинаковым паролем будут разные ключи.', ], }, { id: 'three-keys', shortTitle: 'Три ключа', title: 'Зачем нужны три ключа?', paragraphs: [ 'Ключ восстановления (Recovery key) нужен для восстановления и самых критичных операций с учётной записью. Его приватная часть никогда не сохраняется на устройстве и при необходимости временно вычисляется из пароля.', 'Ключ блокчейна постоянно хранится на авторизованном устройстве. Он подписывает записи и действия в блокчейне Сияния и является исходным ключом для штатных пользовательских кошельков.', 'Ключ доступа (client key) также постоянно хранится на устройстве. Он используется для входа на сервер, работы устройства и защищённых личных сообщений.', 'Сейчас после обычного входа оба рабочих ключа — ключ блокчейна и ключ доступа — сохраняются автоматически. Отдельного ограниченного режима в текущей версии нет.', ], }, { id: 'generation-time', shortTitle: 'Зачем время', title: 'Зачем нужна заметная пауза при генерации?', paragraphs: [ 'Генерация специально сделана не мгновенной. Это усложняет массовый подбор паролей.', 'Argon2id расходует и время, и память, поэтому атаки на GPU и видеокартах становятся заметно дороже и медленнее.', 'Небольшая задержка при создании секрета здесь работает как дополнительная защита.', ], }, { id: 'strong-password', shortTitle: 'Какой пароль', title: 'Какой пароль считается надёжным?', paragraphs: [ 'Минимально разумный уровень сейчас начинается примерно от 8 символов.', 'Хороший практический ориентир для большинства людей: 12 символов и больше. Пароль у нас может быть длиной до 256 символов.', 'Используйте уникальный пароль, который не применяется в других сервисах.', ], }, { id: 'why-own-password', shortTitle: 'Зачем свой', title: 'Почему лучше иметь свой пароль и свои ключи?', paragraphs: [ 'Чем дальше, тем проще будет подделывать фотографию, голос, интонацию и даже другие привычные признаки личности с помощью нейросетей.', 'На расстоянии всё сложнее будет понять, что перед вами действительно вы, если опираться только на внешние признаки.', 'Поэтому персональные ключи, которые храните только вы, становятся надёжнее, чем зависимость от сторонней организации, которая держит ключи у себя.', ], }, { id: 'first-server', shortTitle: 'Первый сервер', title: 'Что такое первый сервер Сияния?', paragraphs: [ 'Первый сервер Сияния это тот сервер, на который вам будут писать и звонить в самом начале. При регистрации он записывается как ваш первый сервер доступа.', 'Позже вы сможете сменить сервер, а ваши данные останутся с вами. В будущем серверов может быть несколько одновременно.', 'Если серверов несколько, данные между ними будут синхронизироваться автоматически. Если добавляете новый сервер и убираете старый, просто дождитесь завершения синхронизации перед отключением старого.', 'Если у вас не остаётся ни одного сервера, синхронизации, конечно, не будет, пока не появится хотя бы один активный сервер снова.', ], }, { id: 'connect-device', shortTitle: 'Подключить по коду', title: 'Как подключить другое устройство?', paragraphs: [ 'На новом устройстве выберите вход через другое устройство и получите код подключения.', 'На уже авторизованном устройстве откройте: Профиль → Настройки → Устройства → Подключить по коду. Введите или выберите соответствующий код и подтвердите подключение.', 'При подтверждении нового устройства автоматически передаются два рабочих ключа: ключ доступа и ключ блокчейна. Приватный ключ восстановления не передаётся никогда.', ], }, { id: 'hardware-device', shortTitle: 'ESP32', title: 'Нужно ли отдельное устройство для ключей?', paragraphs: [ 'Идеальный вариант для важных ключей: отдельное физическое устройство, которое вы контролируете сами.', 'Если пока не хотите покупать отдельное устройство, можно пользоваться телефоном. Но отдельный контроллер или мини-устройство обычно даёт лучший контроль и более понятную модель доверия.', 'Красивая готовая модель Waveshare на ESP32-S3 Touch AMOLED 2.16 стоит около 32 долларов. Есть и более дешёвые варианты на открытых чипах, примерно от 10 до 15 долларов.', 'Если у вас другая модель, под неё можно адаптировать открытую прошивку. Для простых переносов это реально сделать довольно быстро.', ], links: [ { label: 'Документация Waveshare ESP32-S3 Touch AMOLED 2.16', href: 'https://docs.waveshare.com/ESP32-S3-Touch-AMOLED-2.16', }, ], }, { id: 'wallet-device', shortTitle: 'Кошелёк', title: 'Можно ли использовать такое устройство как кошелёк?', paragraphs: [ 'Да. Идея Сияния в том, что устройство может подписывать не только внутренние действия, но и любые другие данные, если для этого добавлена нужная логика.', 'То есть это направление совместимо с моделью аппаратного кошелька: вы храните ключи у себя, а устройство подписывает то, что вы разрешили.', 'Пока ещё не все валюты и сценарии доведены до готового пользовательского уровня, но архитектурно это именно путь к универсальному подписывающему устройству.', ], }, ]; function getTopicById(topicId) { return REGISTRATION_FAQ_TOPICS.find((topic) => topic.id === topicId) || REGISTRATION_FAQ_TOPICS[0]; } export function openRegistrationFaq(navigate, topicId) { state.registrationHelp.selectedTopic = getTopicById(topicId).id; navigate('registration-faq-view'); } export function render({ navigate }) { const selectedTopic = getTopicById(state.registrationHelp?.selectedTopic); const screen = document.createElement('section'); screen.className = 'stack'; const heroCard = document.createElement('div'); heroCard.className = 'card stack registration-faq-hero'; heroCard.innerHTML = `
Короткие ответы на самые частые вопросы о ключах, пароле, первом сервере и доверенных устройствах.
`; const topicCard = document.createElement('div'); topicCard.className = 'card stack registration-faq-topic'; const question = document.createElement('h2'); question.className = 'registration-faq-title'; question.textContent = selectedTopic.title; topicCard.append(question); selectedTopic.paragraphs.forEach((paragraph) => { const p = document.createElement('p'); p.className = 'auth-copy'; p.textContent = paragraph; topicCard.append(p); }); if (Array.isArray(selectedTopic.links) && selectedTopic.links.length > 0) { selectedTopic.links.forEach((linkItem) => { const link = document.createElement('a'); link.className = 'link-card'; link.href = linkItem.href; link.target = '_blank'; link.rel = 'noopener noreferrer'; link.textContent = linkItem.label; topicCard.append(link); }); } const topicsCard = document.createElement('div'); topicsCard.className = 'stack'; const topicsLabel = document.createElement('p'); topicsLabel.className = 'nav-list__title'; topicsLabel.textContent = 'Другие вопросы'; const topicsGrid = document.createElement('div'); topicsGrid.className = 'nav-list'; REGISTRATION_FAQ_TOPICS.filter((topic) => topic.id !== selectedTopic.id).forEach((topic) => { const button = document.createElement('button'); button.className = 'nav-row'; button.type = 'button'; const label = document.createElement('span'); label.className = 'nav-row__label'; label.textContent = topic.title; button.append(label); button.addEventListener('click', () => { state.registrationHelp.selectedTopic = topic.id; navigate('registration-faq-view'); }); topicsGrid.append(button); }); topicsCard.append(topicsLabel, topicsGrid); const actions = document.createElement('div'); actions.className = 'auth-footer-actions'; const backButton = document.createElement('button'); backButton.className = 'ghost-btn'; backButton.type = 'button'; backButton.textContent = 'Назад'; backButton.addEventListener('click', () => navigate('register-view')); const registerButton = document.createElement('button'); registerButton.className = 'primary-btn'; registerButton.type = 'button'; registerButton.textContent = 'К регистрации'; registerButton.addEventListener('click', () => navigate('register-view')); actions.append(backButton, registerButton); screen.append( createTopBar({ title: 'Вопросы о регистрации', back: { label: '←', onClick: () => navigate('register-view') }, }), heroCard, topicCard, topicsCard, actions, ); return screen; }