Миграция PDA на client.key

This commit is contained in:
AidarKC
2026-06-22 21:57:09 +04:00
parent ba348dafb3
commit 5c92b6a734
133 changed files with 941 additions and 30531 deletions
+54 -32
View File
@@ -1,5 +1,5 @@
import { base64ToBytes, importPkcs8Ed25519, sha256Bytes, signBytes } from './crypto-utils.js';
import { extractSeed32FromPkcs8B64 } from './device-key-utils.js';
import { extractSeed32FromPkcs8B64 } from './client-key-utils.js';
import {
SHINE_LOGIN_GUARD_PROGRAM_ID,
SHINE_PAYMENTS_PROGRAM_ID,
@@ -16,8 +16,9 @@ const BLOCKCHAIN_TYPE_MAIN_USER = 1;
const ED25519_PROGRAM_ID = 'Ed25519SigVerify111111111111111111111111111';
const SYSVAR_INSTRUCTIONS_ID = 'Sysvar1nstructions1111111111111111111111111';
const BLOCK_TYPE_RECOVERY_KEY = 0;
const BLOCK_TYPE_ROOT_KEY = 1;
const BLOCK_TYPE_DEVICE_KEY = 2;
const BLOCK_TYPE_CLIENT_KEY = 2;
const BLOCK_TYPE_BLOCKCHAIN_REGISTRY = 3;
const BLOCK_TYPE_SERVER_PROFILE = 30;
const BLOCK_TYPE_ACCESS_SERVERS = 40;
@@ -176,10 +177,11 @@ function serializeCreateUserPdaArgs(args) {
const buf = [];
buf.push(3);
pushStrU8(buf, args.login);
for (const x of args.recoveryKey32) buf.push(x);
for (const x of args.rootKey32) buf.push(x);
pushU64LE(buf, args.createdAtMs);
pushU64LE(buf, BigInt(args.additionalLimitBytes || 0n));
for (const x of args.deviceKey32) buf.push(x);
for (const x of args.clientKey32) buf.push(x);
for (const x of args.blockchainPublicKey32) buf.push(x);
pushStrU8(buf, args.blockchainName);
pushU64LE(buf, args.usedBytes);
@@ -218,13 +220,14 @@ function serializeUpdateUserPdaArgs(args) {
const buf = [];
buf.push(4);
pushStrU8(buf, args.login);
for (const x of args.recoveryKey32) buf.push(x);
for (const x of args.rootKey32) buf.push(x);
pushU64LE(buf, args.createdAtMs);
pushU64LE(buf, args.updatedAtMs);
pushU32LE(buf, args.version);
for (const x of args.prevHash32) buf.push(x);
pushU64LE(buf, args.additionalLimitBytes);
for (const x of args.deviceKey32) buf.push(x);
for (const x of args.clientKey32) buf.push(x);
for (const x of args.blockchainPublicKey32) buf.push(x);
pushStrU8(buf, args.blockchainName);
pushU64LE(buf, args.usedBytes);
@@ -288,8 +291,9 @@ function createPdaState({
updatedAtMs,
recordNumber,
prevRecordHash,
recoveryKey,
rootKey,
deviceKey,
clientKey,
blockchain,
isServer,
addressFormatType,
@@ -313,8 +317,10 @@ function createPdaState({
recordNumber,
prevRecordHash,
login,
recoveryKey,
rootKey,
deviceKey,
clientKey,
clientKey: clientKey,
blockchain,
isServer: Boolean(isServer),
serverProfile,
@@ -373,8 +379,9 @@ export function parseShineUserPda(dataBytes) {
const login = reader.readStrU8();
const blocksCount = reader.readU8();
let recoveryKey = null;
let rootKey = null;
let deviceKey = null;
let clientKey = null;
let blockchain = null;
let isServer = false;
let addressFormatType = 0;
@@ -390,12 +397,16 @@ export function parseShineUserPda(dataBytes) {
const blockType = reader.readU8();
reader.readU8();
if (blockType === BLOCK_TYPE_RECOVERY_KEY) {
recoveryKey = reader.readBytes(32);
continue;
}
if (blockType === BLOCK_TYPE_ROOT_KEY) {
rootKey = reader.readBytes(32);
continue;
}
if (blockType === BLOCK_TYPE_DEVICE_KEY) {
deviceKey = reader.readBytes(32);
if (blockType === BLOCK_TYPE_CLIENT_KEY) {
clientKey = reader.readBytes(32);
continue;
}
if (blockType === BLOCK_TYPE_BLOCKCHAIN_REGISTRY) {
@@ -466,7 +477,7 @@ export function parseShineUserPda(dataBytes) {
throw new Error(`Неизвестный блок PDA: ${blockType}`);
}
if (!rootKey || !deviceKey || !blockchain) {
if (!recoveryKey || !rootKey || !clientKey || !blockchain) {
throw new Error('В PDA отсутствуют обязательные блоки');
}
@@ -478,8 +489,9 @@ export function parseShineUserPda(dataBytes) {
updatedAtMs,
recordNumber,
prevRecordHash,
recoveryKey,
rootKey,
deviceKey,
clientKey,
blockchain,
isServer,
addressFormatType,
@@ -507,8 +519,9 @@ export function serializeUnsignedRecordFromState(stateLike) {
updatedAtMs: stateLike.updatedAtMs,
recordNumber: stateLike.recordNumber,
prevRecordHash: stateLike.prevRecordHash,
recoveryKey: stateLike.recoveryKey,
rootKey: stateLike.rootKey,
deviceKey: stateLike.deviceKey,
clientKey: stateLike.clientKey ?? stateLike.clientKey,
blockchain: stateLike.blockchain,
isServer: stateLike.isServer,
addressFormatType: stateLike.addressFormatType ?? stateLike.serverProfile?.addressFormatType,
@@ -527,13 +540,16 @@ export function serializeUnsignedRecordFromState(stateLike) {
pushU32LE(buf, state.recordNumber);
for (const x of state.prevRecordHash) buf.push(x);
pushStrU8(buf, state.login);
buf.push(state.isServer ? 7 : 6);
buf.push(state.isServer ? 8 : 7);
buf.push(BLOCK_TYPE_RECOVERY_KEY, 0);
for (const x of state.recoveryKey) buf.push(x);
buf.push(BLOCK_TYPE_ROOT_KEY, 0);
for (const x of state.rootKey) buf.push(x);
buf.push(BLOCK_TYPE_DEVICE_KEY, 0);
for (const x of state.deviceKey) buf.push(x);
buf.push(BLOCK_TYPE_CLIENT_KEY, 0);
for (const x of state.clientKey) buf.push(x);
buf.push(BLOCK_TYPE_BLOCKCHAIN_REGISTRY, 0, 1, state.blockchain.blockchainType);
pushStrU8(buf, state.blockchain.blockchainName);
@@ -656,14 +672,15 @@ async function buildCreateContext({ login, keyBundle, solanaEndpoint }) {
const [economyConfigPda] = solana.PublicKey.findProgramAddressSync([enc.encode(SHINE_USERS_ECONOMY_CONFIG_SEED)], usersProgram);
const [inflowVault] = solana.PublicKey.findProgramAddressSync([enc.encode(SHINE_PAYMENTS_INFLOW_VAULT_SEED)], paymentsProgram);
const recoveryKey32 = base64ToBytes(keyBundle.recoveryPair.publicKeyB64);
const rootKey32 = base64ToBytes(keyBundle.rootPair.publicKeyB64);
const blockchainKey32 = base64ToBytes(keyBundle.blockchainPair.publicKeyB64);
const deviceKey32 = base64ToBytes(keyBundle.devicePair.publicKeyB64);
const clientKey32 = base64ToBytes(keyBundle.clientPair.publicKeyB64);
const rootPrivKey = await importPkcs8Ed25519(keyBundle.rootPair.privatePkcs8B64);
const bchPrivKey = await importPkcs8Ed25519(keyBundle.blockchainPair.privatePkcs8B64);
const deviceSeed32 = extractSeed32FromPkcs8B64(keyBundle.devicePair.privatePkcs8B64);
const deviceKeypair = solana.Keypair.fromSeed(deviceSeed32);
const clientSeed32 = extractSeed32FromPkcs8B64(keyBundle.clientPair.privatePkcs8B64);
const clientKeypair = solana.Keypair.fromSeed(clientSeed32);
return {
cleanLogin,
@@ -678,12 +695,13 @@ async function buildCreateContext({ login, keyBundle, solanaEndpoint }) {
userPda,
economyConfigPda,
inflowVault,
recoveryKey32,
rootKey32,
blockchainKey32,
deviceKey32,
clientKey32,
rootPrivKey,
bchPrivKey,
deviceKeypair,
clientKeypair,
};
}
@@ -720,8 +738,9 @@ async function createShineUserPdaOnSolana({
updatedAtMs: createdAtMs,
recordNumber: 0,
prevRecordHash: zeroHash32,
recoveryKey: ctx.recoveryKey32,
rootKey: ctx.rootKey32,
deviceKey: ctx.deviceKey32,
clientKey: ctx.clientKey32,
blockchain: createBlockchainState({
blockchainName,
blockchainPublicKey: ctx.blockchainKey32,
@@ -749,10 +768,11 @@ async function createShineUserPdaOnSolana({
const ixData = serializeCreateUserPdaArgs({
login: cleanLogin,
recoveryKey32: ctx.recoveryKey32,
rootKey32: ctx.rootKey32,
createdAtMs,
additionalLimitBytes: 0n,
deviceKey32: ctx.deviceKey32,
clientKey32: ctx.clientKey32,
blockchainPublicKey32: ctx.blockchainKey32,
blockchainName,
usedBytes: 0n,
@@ -785,7 +805,7 @@ async function createShineUserPdaOnSolana({
const createIx = new ctx.solana.TransactionInstruction({
programId: ctx.usersProgram,
keys: [
{ pubkey: ctx.deviceKeypair.publicKey, isSigner: true, isWritable: true },
{ pubkey: ctx.clientKeypair.publicKey, isSigner: true, isWritable: true },
{ pubkey: ctx.userPda, isSigner: false, isWritable: true },
{ pubkey: ctx.solana.SystemProgram.programId, isSigner: false, isWritable: false },
{ pubkey: ctx.inflowVault, isSigner: false, isWritable: true },
@@ -799,7 +819,7 @@ async function createShineUserPdaOnSolana({
const signature = await ctx.solana.sendAndConfirmTransaction(
ctx.connection,
new ctx.solana.Transaction().add(ed25519RootIx, ed25519BchIx, createIx),
[ctx.deviceKeypair],
[ctx.clientKeypair],
{ commitment: 'confirmed' },
);
@@ -848,7 +868,7 @@ export async function updateShineUserPdaOnSolana({
login,
solanaEndpoint,
rootPrivatePkcs8B64,
devicePrivatePkcs8B64,
clientPrivatePkcs8B64,
blockchainPrivatePkcs8B64,
additionalLimitBytes = 0n,
nextUsedBytes,
@@ -882,8 +902,8 @@ export async function updateShineUserPdaOnSolana({
const [userPda] = solana.PublicKey.findProgramAddressSync([enc.encode(SHINE_USERS_USER_PDA_SEED_PREFIX), enc.encode(cleanLogin)], usersProgram);
const [economyPda] = solana.PublicKey.findProgramAddressSync([enc.encode(SHINE_USERS_ECONOMY_CONFIG_SEED)], usersProgram);
const [inflowVault] = solana.PublicKey.findProgramAddressSync([enc.encode(SHINE_PAYMENTS_INFLOW_VAULT_SEED)], paymentsProgram);
const deviceSeed32 = extractSeed32FromPkcs8B64(devicePrivatePkcs8B64);
const deviceKeypair = solana.Keypair.fromSeed(deviceSeed32);
const clientSeed32 = extractSeed32FromPkcs8B64(clientPrivatePkcs8B64);
const clientKeypair = solana.Keypair.fromSeed(clientSeed32);
const lastBlockStateBytes = buildLastBlockStateBytes(
cleanLogin,
@@ -928,8 +948,9 @@ export async function updateShineUserPdaOnSolana({
updatedAtMs,
recordNumber: newRecordNumber,
prevRecordHash: prevHash,
recoveryKey: current.recoveryKey,
rootKey: current.rootKey,
deviceKey: current.deviceKey,
clientKey: current.clientKey ?? current.clientKey,
blockchain: createBlockchainState({
blockchainName: currentBch.blockchainName,
blockchainPublicKey: currentBch.blockchainPublicKey,
@@ -957,13 +978,14 @@ export async function updateShineUserPdaOnSolana({
const ixData = serializeUpdateUserPdaArgs({
login: cleanLogin,
recoveryKey32: current.recoveryKey,
rootKey32: current.rootKey,
createdAtMs: current.createdAtMs,
updatedAtMs,
version: newRecordNumber,
prevHash32: prevHash,
additionalLimitBytes: addLimit,
deviceKey32: current.deviceKey,
clientKey32: current.clientKey,
blockchainPublicKey32: currentBch.blockchainPublicKey,
blockchainName: currentBch.blockchainName,
usedBytes: effectiveUsed,
@@ -996,7 +1018,7 @@ export async function updateShineUserPdaOnSolana({
const updateIx = new solana.TransactionInstruction({
programId: usersProgram,
keys: [
{ pubkey: deviceKeypair.publicKey, isSigner: true, isWritable: true },
{ pubkey: clientKeypair.publicKey, isSigner: true, isWritable: true },
{ pubkey: userPda, isSigner: false, isWritable: true },
{ pubkey: solana.SystemProgram.programId, isSigner: false, isWritable: false },
{ pubkey: inflowVault, isSigner: false, isWritable: true },
@@ -1011,7 +1033,7 @@ export async function updateShineUserPdaOnSolana({
const signature = await solana.sendAndConfirmTransaction(
connection,
new solana.Transaction().add(computeIx, heapIx, edIxRoot, edIxBch, updateIx),
[deviceKeypair],
[clientKeypair],
{ commitment: 'confirmed' },
);
@@ -1040,7 +1062,7 @@ export async function updateServerOnSolana({
login,
solanaEndpoint,
rootPrivatePkcs8B64: keyBundle.rootPair.privatePkcs8B64,
devicePrivatePkcs8B64: keyBundle.devicePair.privatePkcs8B64,
clientPrivatePkcs8B64: keyBundle.clientPair.privatePkcs8B64,
serverProfile: {
addressFormatType,
addressFormatVersion,