Промежуточный коммит: состояние до нормальной Solana-first регистрации

This commit is contained in:
AidarKC
2026-05-27 18:33:26 +04:00
parent b345900459
commit 6f0bb01b61
17 changed files with 661 additions and 72 deletions
+70 -6
View File
@@ -2,7 +2,8 @@ import { WsJsonClient } from './ws-client.js';
import {
base64ToBytes,
bytesToBase64,
deriveEd25519FromPassword,
deriveEd25519FromMasterSecret,
deriveMasterSecretFromPassword,
exportEd25519PublicKeyB64,
exportPkcs8B64,
generateEd25519Pair,
@@ -671,6 +672,8 @@ export class AuthService {
this.ws = new WsJsonClient(this.serverUrl);
this.headerHashCache = new Map();
this.writeLocks = new Map();
this.passwordKeyBundleCache = new Map();
this.passwordKeyBundleInFlight = new Map();
}
async reconnect(serverUrl) {
@@ -706,13 +709,56 @@ export class AuthService {
return payload.exists !== true;
}
async derivePasswordKeyBundle(login, password) {
async derivePasswordKeyBundle(login, password, options = {}) {
const normalizedLogin = String(login ?? '');
const normalizedPassword = String(password ?? '');
const rootPair = await deriveEd25519FromPassword(normalizedPassword, 'root.key', { login: normalizedLogin });
const blockchainPair = await deriveEd25519FromPassword(normalizedPassword, 'bch.key', { login: normalizedLogin });
const devicePair = await deriveEd25519FromPassword(normalizedPassword, 'dev.key', { login: normalizedLogin });
return { rootPair, blockchainPair, devicePair };
const cacheKey = `${normalizedLogin}\n${normalizedPassword}`;
const onProgress = typeof options?.onProgress === 'function' ? options.onProgress : null;
const isCancelled = typeof options?.isCancelled === 'function' ? options.isCancelled : null;
if (this.passwordKeyBundleCache.has(cacheKey)) {
if (onProgress) onProgress({ percent: 100, stage: 'cached', message: 'Ключи уже сгенерированы в памяти.' });
return this.passwordKeyBundleCache.get(cacheKey);
}
if (this.passwordKeyBundleInFlight.has(cacheKey)) {
return this.passwordKeyBundleInFlight.get(cacheKey);
}
const task = (async () => {
if (onProgress) onProgress({ percent: 3, stage: 'prepare', message: 'Подготовка параметров генерации...' });
if (isCancelled && isCancelled()) throw new Error('DERIVE_CANCELLED');
const masterSecret = await deriveMasterSecretFromPassword(normalizedPassword, {
login: normalizedLogin,
onProgress: (value01) => {
if (!onProgress) return;
const v = Math.max(0, Math.min(1, Number(value01) || 0));
const percent = Math.round(5 + (v * 88));
onProgress({ percent, stage: 'secret', message: 'Генерация секрета из пароля...' });
},
});
if (isCancelled && isCancelled()) throw new Error('DERIVE_CANCELLED');
if (onProgress) onProgress({ percent: 94, stage: 'derive', message: 'Вычисление root key...' });
const rootPair = await deriveEd25519FromMasterSecret(masterSecret, 'root.key');
if (isCancelled && isCancelled()) throw new Error('DERIVE_CANCELLED');
if (onProgress) onProgress({ percent: 96, stage: 'derive', message: 'Вычисление blockchain key...' });
const blockchainPair = await deriveEd25519FromMasterSecret(masterSecret, 'bch.key');
if (isCancelled && isCancelled()) throw new Error('DERIVE_CANCELLED');
if (onProgress) onProgress({ percent: 98, stage: 'derive', message: 'Вычисление device key...' });
const devicePair = await deriveEd25519FromMasterSecret(masterSecret, 'dev.key');
const result = { rootPair, blockchainPair, devicePair };
this.passwordKeyBundleCache.set(cacheKey, result);
if (onProgress) onProgress({ percent: 100, stage: 'done', message: 'Ключи сгенерированы.' });
return result;
})().finally(() => {
this.passwordKeyBundleInFlight.delete(cacheKey);
});
this.passwordKeyBundleInFlight.set(cacheKey, task);
return task;
}
async createAuthSession(login, keyBundle) {
@@ -784,6 +830,24 @@ export class AuthService {
return { ...session, keyBundle };
}
async registerUserWithKeyBundle(login, keyBundle) {
const cleanLogin = (login || '').trim();
if (!cleanLogin) throw new Error('Введите логин');
const addResp = await this.ws.request('AddUser', {
login: cleanLogin,
blockchainName: `${cleanLogin}-${BCH_SUFFIX}`,
solanaKey: keyBundle.devicePair.publicKeyB64,
blockchainKey: keyBundle.blockchainPair.publicKeyB64,
deviceKey: keyBundle.devicePair.publicKeyB64,
bchLimit: 1000000,
});
if (addResp.status !== 200) throw opError('AddUser', addResp);
const session = await this.createAuthSession(cleanLogin, keyBundle);
return { ...session, keyBundle };
}
async createSessionForExistingUser(login, password) {
const cleanLogin = (login || '').trim();
if (!cleanLogin) throw new Error('Введите логин');