Реализовать SHiNE_DM v1 с E2EE и tombstone

This commit is contained in:
AidarKC
2026-07-07 18:07:10 +04:00
parent 9c588bd9b5
commit 1b7a2a8f7c
33 changed files with 1636 additions and 445 deletions
+54
View File
@@ -1,6 +1,7 @@
const encoder = new TextEncoder();
const WEB_CRYPTO_REQUIRED_MESSAGE = 'Регистрация и подпись блоков требуют WebCrypto (crypto.subtle). Откройте приложение через HTTPS или localhost в современном браузере и повторите попытку.';
import { argon2idAsync } from 'https://esm.sh/@noble/hashes@1.8.0/argon2.js';
import { edwardsToMontgomeryPriv, edwardsToMontgomeryPub, x25519 } from 'https://esm.sh/@noble/curves@1.8.1/ed25519';
const SHINE_KEY_DERIVATION_PREFIX = 'SHiNE-key';
function getCryptoApi() {
@@ -340,3 +341,56 @@ export async function signBytes(privateKey, bytes) {
const signature = await getSubtleApi().sign({ name: 'Ed25519' }, privateKey, bytes);
return new Uint8Array(signature);
}
export function extractEd25519SeedFromPkcs8B64(pkcs8B64) {
const bytes = base64ToBytes(String(pkcs8B64 || '').trim());
if (bytes.length < 32) throw new Error('Некорректный PKCS8 ключ client.key');
return bytes.slice(bytes.length - 32);
}
export function ed25519SeedToX25519Private(seed32) {
const seed = seed32 instanceof Uint8Array ? seed32 : new Uint8Array(seed32 || []);
if (seed.length !== 32) throw new Error('Seed Ed25519 должен быть 32 байта');
return new Uint8Array(edwardsToMontgomeryPriv(seed));
}
export function ed25519PublicToX25519Public(ed25519Pub32) {
const pub = ed25519Pub32 instanceof Uint8Array ? ed25519Pub32 : new Uint8Array(ed25519Pub32 || []);
if (pub.length !== 32) throw new Error('Публичный Ed25519 ключ должен быть 32 байта');
return new Uint8Array(edwardsToMontgomeryPub(pub));
}
export function x25519RandomPrivateKey() {
return new Uint8Array(x25519.utils.randomPrivateKey());
}
export function x25519PublicFromPrivate(privateKey32) {
const key = privateKey32 instanceof Uint8Array ? privateKey32 : new Uint8Array(privateKey32 || []);
if (key.length !== 32) throw new Error('Приватный X25519 ключ должен быть 32 байта');
return new Uint8Array(x25519.getPublicKey(key));
}
export function x25519SharedSecret(privateKey32, publicKey32) {
const priv = privateKey32 instanceof Uint8Array ? privateKey32 : new Uint8Array(privateKey32 || []);
const pub = publicKey32 instanceof Uint8Array ? publicKey32 : new Uint8Array(publicKey32 || []);
if (priv.length !== 32 || pub.length !== 32) {
throw new Error('X25519 ключи должны быть по 32 байта');
}
return new Uint8Array(x25519.getSharedSecret(priv, pub));
}
export async function hkdfSha256(ikmBytes, saltBytes, infoBytes, outLen) {
const subtle = getSubtleApi();
const baseKey = await subtle.importKey('raw', ikmBytes, 'HKDF', false, ['deriveBits']);
const bits = await subtle.deriveBits(
{
name: 'HKDF',
hash: 'SHA-256',
salt: saltBytes instanceof Uint8Array ? saltBytes : new Uint8Array(saltBytes || []),
info: infoBytes instanceof Uint8Array ? infoBytes : new Uint8Array(infoBytes || []),
},
baseKey,
Number(outLen) * 8,
);
return new Uint8Array(bits);
}