SHA256
homeserver: рендейм subserver→homeserver, документ деривации ключей, запрет пустого пароля
Основное (наша работа в этой сессии): - Переименование «subserver» → «homeserver» по всему проекту: основной ESP32-скетч (папка shine_subserver_ui → shine_homeserver_ui, .ino, flash-скрипт, режим burn.sh homeserver-ui), скетч lvgl_nav_minimal_test (ключ homeserver.key:<имя>), spec-доки reference/*, формат PDA (терминология session_type=100 «Homeserver пользователя»), константа SESSION_TYPE_HOMESERVER в JS и Rust (значение 100 не менялось, формат не затронут), pending/future доки, AGENTS.md, DAO-док. Сохранены отдельный lvgl_subserver_touch_test и историческая пометка о рендейме в DERIVATION.md. - Новый источник истины по деривации ключей: Dev_Docs/Keys/DERIVATION.md (Argon2id-секрет из пароля, формула Ed25519(SHA-256(base64(secret)|suffix)), суффиксы root/bch/dev/homeserver.key, Solana-ключ = dev.key). Уточнены роли root (главный/master) и dev (пополняемый кошелёк) в Dev_Docs/Keys/README.md. - UI: убран легаси-путь пустого пароля (derivePasswordSeed и др.), deriveMasterSecretFromPassword бросает ошибку на пустом пароле, register-view блокирует пустой пароль; экран пополнения переведён на канонический device-адрес из preGeneratedKeyBundle (удалён расходящийся deriveWalletFromPassword). Включены также параллельные правки Solana-аудита №3 (были в рабочем дереве, переплетены в lib.rs): - shine_users: defense-in-depth «строгий список аккаунтов» (require!(it.next().is_none())) в init/update economy config и create/update user PDA, плюс описание в doc/programs/shine_users.md; - Dev_Docs/audit/Solana-audit-3-by-Claude-12июня2026.md. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
cf6a2830c8
commit
42dcf6970d
@@ -116,12 +116,6 @@ export async function sha256Text(text) {
|
||||
return sha256Bytes(utf8Bytes(text));
|
||||
}
|
||||
|
||||
export async function derivePasswordSeed(password, suffix) {
|
||||
const base = await sha256Text(password || '');
|
||||
const concat = `${bytesToBase64(base)}${suffix}`;
|
||||
return sha256Text(concat);
|
||||
}
|
||||
|
||||
function normalizeLoginForKdf(login) {
|
||||
return String(login || '').trim().toLowerCase();
|
||||
}
|
||||
@@ -134,21 +128,6 @@ async function makeArgon2Salt(login, suffix) {
|
||||
return digest.slice(0, 16);
|
||||
}
|
||||
|
||||
async function derivePasswordSeedArgon2id({ login, password, suffix }) {
|
||||
const normalizedLogin = normalizeLoginForKdf(login);
|
||||
const normalizedPassword = String(password ?? '');
|
||||
const normalizedSuffix = String(suffix || '').trim();
|
||||
const salt = await makeArgon2Salt(normalizedLogin, normalizedSuffix);
|
||||
const passBytes = utf8Bytes(`${normalizedLogin}\n${normalizedPassword}`);
|
||||
const out = await argon2idAsync(passBytes, salt, {
|
||||
t: 2,
|
||||
m: 65536,
|
||||
p: 1,
|
||||
dkLen: 32,
|
||||
});
|
||||
return new Uint8Array(out);
|
||||
}
|
||||
|
||||
async function deriveMasterSecretArgon2id({ login, password, onProgress }) {
|
||||
const normalizedLogin = normalizeLoginForKdf(login);
|
||||
const normalizedPassword = String(password ?? '');
|
||||
@@ -177,38 +156,13 @@ function ed25519Pkcs8FromSeed(seed32) {
|
||||
return out;
|
||||
}
|
||||
|
||||
export async function deriveEd25519FromPassword(password, suffix, options = {}) {
|
||||
const normalizedPassword = String(password ?? '');
|
||||
const normalizedLogin = String(options?.login ?? '');
|
||||
const useLegacyEmptyPassword = normalizedPassword.length === 0;
|
||||
const seed = useLegacyEmptyPassword
|
||||
? await derivePasswordSeed(normalizedPassword, suffix)
|
||||
: await derivePasswordSeedArgon2id({
|
||||
login: normalizedLogin,
|
||||
password: normalizedPassword,
|
||||
suffix,
|
||||
});
|
||||
const pkcs8 = ed25519Pkcs8FromSeed(seed);
|
||||
const subtle = getSubtleApi();
|
||||
const privateKey = await subtle.importKey('pkcs8', pkcs8, { name: 'Ed25519' }, true, ['sign']);
|
||||
const jwk = await subtle.exportKey('jwk', privateKey);
|
||||
if (!jwk.x) throw new Error('Не удалось получить публичный ключ Ed25519');
|
||||
|
||||
return {
|
||||
privateKey,
|
||||
publicKeyB64: bytesToBase64(base64ToBytes(base64UrlToBase64(jwk.x))),
|
||||
privatePkcs8B64: bytesToBase64(pkcs8),
|
||||
};
|
||||
}
|
||||
|
||||
export async function deriveMasterSecretFromPassword(password, options = {}) {
|
||||
const normalizedPassword = String(password ?? '');
|
||||
const normalizedLogin = String(options?.login ?? '');
|
||||
const onProgress = typeof options?.onProgress === 'function' ? options.onProgress : undefined;
|
||||
if (normalizedPassword.length === 0) {
|
||||
const legacy = await derivePasswordSeed(normalizedPassword, 'master.secret');
|
||||
if (onProgress) onProgress(1);
|
||||
return legacy;
|
||||
// Пустой пароль запрещён: упрощённый легаси-путь убран, регистрация/вход требуют непустой пароль.
|
||||
throw new Error('Пустой пароль запрещён: регистрация и вход требуют непустой пароль.');
|
||||
}
|
||||
return deriveMasterSecretArgon2id({
|
||||
login: normalizedLogin,
|
||||
|
||||
@@ -25,7 +25,7 @@ const BLOCK_TYPE_SESSIONS = 50;
|
||||
const BLOCK_TYPE_TRUSTED_STATE = 70;
|
||||
const SESSIONS_MODE_MIXED = 1;
|
||||
const SESSION_TYPE_USER = 1;
|
||||
const SESSION_TYPE_SUBSERVER = 100;
|
||||
const SESSION_TYPE_HOMESERVER = 100;
|
||||
|
||||
let solanaLibPromise = null;
|
||||
function loadSolanaLib() {
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { deriveEd25519FromPassword } from './crypto-utils.js';
|
||||
import { extractDeviceKey32FromStoredValue } from './device-key-utils.js';
|
||||
import { loadEncryptedUserSecrets } from './key-vault.js';
|
||||
import { SOLANA_ENDPOINT_DEFAULT } from '../solana-programs.js';
|
||||
@@ -68,17 +67,6 @@ async function keypairFromPkcs8(pkcs8B64) {
|
||||
return solana.Keypair.fromSeed(seed32);
|
||||
}
|
||||
|
||||
export async function deriveWalletFromPassword(password) {
|
||||
const keyBundle = await deriveEd25519FromPassword(String(password ?? ''), 'dev.key');
|
||||
const keypair = await keypairFromPkcs8(keyBundle.privatePkcs8B64);
|
||||
return {
|
||||
address: keypair.publicKey.toBase58(),
|
||||
keypair,
|
||||
devicePublicKeyB64: keyBundle.publicKeyB64,
|
||||
devicePrivatePkcs8B64: keyBundle.privatePkcs8B64,
|
||||
};
|
||||
}
|
||||
|
||||
export async function createRandomSolanaWallet() {
|
||||
const solana = await loadSolanaLib();
|
||||
const keypair = solana.Keypair.generate();
|
||||
|
||||
Reference in New Issue
Block a user