import { renderHeader } from '../components/header.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: [ 'Ключи хранятся только у вас: на вашем устройстве, на доверенных устройствах или на отдельном внешнем устройстве, которое вы контролируете сами.', 'SHiNE не хранит ваши приватные ключи на сервере. Сервер помогает с доставкой и синхронизацией, но не владеет вашим секретом.', 'Если захотите, ключи можно держать на отдельном полностью программируемом устройстве с открытым кодом, например на ESP32-контроллере.', ], }, { id: 'reliability', shortTitle: 'Надёжность', title: 'Насколько это надёжно?', paragraphs: [ 'Мы делаем ставку на открытость: клиентский код открыт, серверный код открыт, протокол открыт. Это позволяет проверять систему независимо, а не верить обещаниям на слово.', 'Мы рекомендуем использовать браузеры с открытым исходным кодом. Позже планируются отдельные приложения для Android, iPhone, Ubuntu Touch и Linux, тоже с открытым кодом.', 'Проект распространяется по лицензии AGPL v3. Часть важных данных и регистрационных записей также опирается на блокчейн-слой, чтобы уменьшать зависимость от одной закрытой стороны.', ], }, { id: 'key-derivation', shortTitle: 'Деривация', title: 'Как генерируются ключи и что делает пароль?', paragraphs: [ 'Из вашего логина и пароля с помощью Argon2id вычисляется специальный секрет.', 'Уже из этого секрета детерминированно строятся четыре основных ключа: recovery key, root key, blockchain key и client key.', 'Это значит, что логин и пароль не просто проверяются на сервере, а реально участвуют в создании ваших ключей. У разных логинов даже с одинаковым паролем будут разные ключи.', ], }, { id: 'three-keys', shortTitle: 'Три ключа', title: 'Зачем нужны три ключа?', paragraphs: [ 'Root key нужен для управления вашей основной публичной записью и важными изменениями личности, включая обновление главной публичной части в Solana.', 'Blockchain key нужен для подписания действий и записей в блокчейне SHiNE.', 'Client key нужен для входов и работы конкретного устройства. Благодаря разделению ключей можно точнее выдавать права одним устройствам и не выдавать другим.', 'Если не хочется в это вникать, обычно можно просто сохранить все ключи на своём устройстве. Для большинства обычных сценариев на iPhone, Android и Linux это вполне практично. Для больших сумм или повышенного риска лучше отдельное внешнее устройство.', ], }, { id: 'generation-time', shortTitle: 'Зачем время', title: 'Зачем нужна заметная пауза при генерации?', paragraphs: [ 'Генерация специально сделана не мгновенной. Это усложняет массовый подбор паролей.', 'Argon2id расходует и время, и память, поэтому атаки на GPU и видеокартах становятся заметно дороже и медленнее.', 'Небольшая задержка при создании секрета здесь работает как дополнительная защита.', ], }, { id: 'strong-password', shortTitle: 'Какой пароль', title: 'Какой пароль считается надёжным?', paragraphs: [ 'Минимально разумный уровень сейчас начинается примерно от 8 символов.', 'Хороший практический ориентир для большинства людей: 12 символов и больше. Пароль у нас может быть длиной до 256 символов.', 'Если вам удобнее думать словами, можно использовать режим из 12 полей ниже: слова просто склеиваются в один длинный пароль, и система не проверяет орфографию.', ], }, { id: 'one-or-twelve', shortTitle: '1 или 12 слов', title: 'Чем отличается один пароль от режима 12 слов?', paragraphs: [ 'Технически ничем: это один и тот же пароль. Режим 12 слов нужен только для удобства запоминания и ввода.', 'Можно заполнить все 12 полей, можно только первые 6, можно использовать слова от другого кошелька, разные языки и любые нестандартные символы.', 'Главное помнить, что в конце всё равно получается одна строка длиной до 256 символов.', ], }, { id: 'why-own-password', shortTitle: 'Зачем свой', title: 'Почему лучше иметь свой пароль и свои ключи?', paragraphs: [ 'Чем дальше, тем проще будет подделывать фотографию, голос, интонацию и даже другие привычные признаки личности с помощью нейросетей.', 'На расстоянии всё сложнее будет понять, что перед вами действительно вы, если опираться только на внешние признаки.', 'Поэтому персональные ключи, которые храните только вы, становятся надёжнее, чем зависимость от сторонней организации, которая держит ключи у себя.', ], }, { id: 'first-server', shortTitle: 'Первый сервер', title: 'Что такое первый сервер SHiNE?', paragraphs: [ 'Первый сервер SHiNE это тот сервер, на который вам будут писать и звонить в самом начале. При регистрации он записывается как ваш первый сервер доступа.', 'Позже вы сможете сменить сервер, а ваши данные останутся с вами. В будущем серверов может быть несколько одновременно.', 'Если серверов несколько, данные между ними будут синхронизироваться автоматически. Если добавляете новый сервер и убираете старый, просто дождитесь завершения синхронизации перед отключением старого.', 'Если у вас не остаётся ни одного сервера, синхронизации, конечно, не будет, пока не появится хотя бы один активный сервер снова.', ], }, { 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: [ 'Да. Идея SHiNE в том, что устройство может подписывать не только внутренние действия, но и любые другие данные, если для этого добавлена нужная логика.', 'То есть это направление совместимо с моделью аппаратного кошелька: вы храните ключи у себя, а устройство подписывает то, что вы разрешили.', 'Пока ещё не все валюты и сценарии доведены до готового пользовательского уровня, но архитектурно это именно путь к универсальному подписывающему устройству.', ], }, ]; 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 = 'card stack'; const topicsLabel = document.createElement('p'); topicsLabel.className = 'field-label'; topicsLabel.textContent = 'Другие вопросы'; const topicsGrid = document.createElement('div'); topicsGrid.className = 'registration-faq-grid'; REGISTRATION_FAQ_TOPICS.forEach((topic) => { const button = document.createElement('button'); button.className = topic.id === selectedTopic.id ? 'secondary-btn' : 'ghost-btn'; button.type = 'button'; button.textContent = topic.shortTitle; 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( renderHeader({ title: 'Вопросы о регистрации', leftAction: { label: '←', onClick: () => navigate('register-view') }, }), heroCard, topicCard, topicsCard, actions, ); return screen; }