Новый протокол Solana PDA 1.2

This commit is contained in:
AidarKC
2026-09-26 11:01:14 +03:00
parent 6e5b57fd7c
commit c997057a37
47 changed files with 2392 additions and 4480 deletions
@@ -52,113 +52,84 @@ function readStrU8(bytes, cursorRef) {
function parseServerFieldsFromUserPda(dataBytes) {
const bytes = dataBytes instanceof Uint8Array ? dataBytes : new Uint8Array(dataBytes || []);
if (bytes.length < 5) throw new Error('Некорректный формат PDA');
if (bytes.length < 9) throw new Error('Некорректный формат PDA');
const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
const cursorRef = { value: 0 };
const readU16 = () => { if (cursorRef.value + 2 > bytes.length) throw new Error('Повреждённый формат PDA'); const v = view.getUint16(cursorRef.value, true); cursorRef.value += 2; return v; };
const readU32 = () => { if (cursorRef.value + 4 > bytes.length) throw new Error('Повреждённый формат PDA'); const v = view.getUint32(cursorRef.value, true); cursorRef.value += 4; return v; };
const readU64 = () => { if (cursorRef.value + 8 > bytes.length) throw new Error('Повреждённый формат PDA'); const v = view.getBigUint64(cursorRef.value, true); cursorRef.value += 8; return v; };
const magic = new TextDecoder().decode(readBytes(bytes, cursorRef, 5));
if (magic !== 'SHiNE') throw new Error('Некорректный формат PDA');
cursorRef.value += 1; // format_major
cursorRef.value += 1; // format_minor
cursorRef.value += 2; // record_len
const formatMajor = readU8(bytes, cursorRef);
const formatMinor = readU8(bytes, cursorRef);
if (formatMajor !== 1 || formatMinor !== 2) throw new Error(`Неподдерживаемый формат PDA ${formatMajor}.${formatMinor}`);
const recordLen = readU16();
if (recordLen < 73 || recordLen > bytes.length) throw new Error('Некорректный record_len PDA');
cursorRef.value += 8; // created_at_ms
cursorRef.value += 8; // updated_at_ms
cursorRef.value += 4; // record_number
cursorRef.value += 32; // prev_record_hash
readStrU8(bytes, cursorRef); // login
const login = readStrU8(bytes, cursorRef);
const blocksCount = readU8(bytes, cursorRef);
let isServer = false;
let serverAddress = '';
let accessServers = [];
let recoveryKey32 = null;
let rootKey32 = null;
let clientKey32 = null;
let blockchainKey32 = null;
let blockchainName = '';
let homeserverSessions = [];
let forkCount = 0;
for (let i = 0; i < blocksCount; i += 1) {
const blockType = readU8(bytes, cursorRef);
cursorRef.value += 1; // block_version
if (blockType === 0 || blockType === 1 || blockType === 2) {
const blockVersion = readU8(bytes, cursorRef);
if (blockVersion !== 0) throw new Error('Неподдерживаемая версия блока PDA');
if (blockType === 1 || blockType === 2) {
const key32 = readBytes(bytes, cursorRef, 32);
if (blockType === 0) recoveryKey32 = key32;
if (blockType === 1) rootKey32 = key32;
if (blockType === 2) clientKey32 = key32;
else clientKey32 = key32;
continue;
}
const payloadLen = readU16();
const payloadEnd = cursorRef.value + payloadLen;
if (payloadEnd > recordLen - 64) throw new Error('Повреждённый variable block PDA');
if (blockType === 3) {
const count = readU8(bytes, cursorRef);
for (let j = 0; j < count; j += 1) {
cursorRef.value += 1;
const currentBlockchainName = readStrU8(bytes, cursorRef);
const currentBlockchainKey32 = readBytes(bytes, cursorRef, 32);
if (!blockchainKey32) {
blockchainKey32 = currentBlockchainKey32;
blockchainName = currentBlockchainName;
}
cursorRef.value += 8 + 8 + 4 + 32 + 64;
const arPresent = readU8(bytes, cursorRef);
if (arPresent === 1) readStrU8(bytes, cursorRef);
forkCount = readU16();
if (forkCount < 1) throw new Error('BlockchainRegistry пуст');
for (let j = 0; j < forkCount; j += 1) {
blockchainKey32 = readBytes(bytes, cursorRef, 32);
readU64(); // created_at_ms fork
readU32(); // paid_limit_bytes
}
continue;
}
if (blockType === 30) {
isServer = readU8(bytes, cursorRef) === 1;
if (isServer) {
cursorRef.value += 1; // address_format_type
cursorRef.value += 1; // address_format_version
serverAddress = readStrU8(bytes, cursorRef);
const syncCount = readU8(bytes, cursorRef);
for (let j = 0; j < syncCount; j += 1) readStrU8(bytes, cursorRef);
}
continue;
}
if (blockType === 40) {
} else if (blockType === 30) {
const addressCount = readU8(bytes, cursorRef);
if (addressCount !== 1) throw new Error('PDA 1.2 допускает ровно один адрес сервера');
readU8(bytes, cursorRef); // address_format_type
readU8(bytes, cursorRef); // address_format_version
serverAddress = readStrU8(bytes, cursorRef);
} else if (blockType === 40) {
const accessCount = readU8(bytes, cursorRef);
if (accessCount > 1) throw new Error('PDA 1.2 допускает максимум один access server');
accessServers = [];
for (let j = 0; j < accessCount; j += 1) accessServers.push(readStrU8(bytes, cursorRef));
continue;
}
if (blockType === 50) {
cursorRef.value += 1;
const sessionsCount = readU8(bytes, cursorRef);
for (let j = 0; j < sessionsCount; j += 1) {
const sessionType = readU8(bytes, cursorRef);
const sessionVersion = readU8(bytes, cursorRef);
const sessionName = readStrU8(bytes, cursorRef);
const sessionPubKey32 = readBytes(bytes, cursorRef, 32);
if (sessionType === 100) {
homeserverSessions.push({
sessionType,
sessionVersion,
sessionName,
sessionPubKeyBase58: new PublicKey(sessionPubKey32).toBase58(),
sessionPubKeyB64: `ed25519/${btoa(String.fromCharCode(...sessionPubKey32))}`,
});
}
}
continue;
}
if (blockType === 70) {
cursorRef.value += 1;
continue;
}
throw new Error(`Неизвестный блок PDA: ${blockType}`);
cursorRef.value = payloadEnd;
}
if (!rootKey32 || !clientKey32 || !blockchainKey32) throw new Error('PDA 1.2 не содержит обязательные ключи');
return {
isServer,
isServer: Boolean(serverAddress),
serverAddress: normalizeHostLike(serverAddress),
accessServers: accessServers.map((value) => normalizeServerLogin(value)).filter(Boolean),
publicKeys: {
recoveryKeyBase58: recoveryKey32 ? new PublicKey(recoveryKey32).toBase58() : '',
rootKeyBase58: rootKey32 ? new PublicKey(rootKey32).toBase58() : '',
clientKeyBase58: clientKey32 ? new PublicKey(clientKey32).toBase58() : '',
blockchainKeyBase58: blockchainKey32 ? new PublicKey(blockchainKey32).toBase58() : '',
blockchainName,
recoveryKeyBase58: '',
rootKeyBase58: new PublicKey(rootKey32).toBase58(),
clientKeyBase58: new PublicKey(clientKey32).toBase58(),
blockchainKeyBase58: new PublicKey(blockchainKey32).toBase58(),
blockchainName: `${normalizeServerLogin(login)}-${String(forkCount).padStart(3, '0')}`,
},
homeserverSessions,
homeserverSessions: [],
};
}