SHA256
Отключить репосты и добавить Solana-модуль
This commit is contained in:
@@ -0,0 +1,328 @@
|
||||
import * as anchor from "@coral-xyz/anchor";
|
||||
import { Program } from "@coral-xyz/anchor";
|
||||
import {
|
||||
Ed25519Program,
|
||||
PublicKey,
|
||||
SYSVAR_INSTRUCTIONS_PUBKEY,
|
||||
SystemProgram,
|
||||
Transaction,
|
||||
} from "@solana/web3.js";
|
||||
import { createHash } from "crypto";
|
||||
import { expect } from "chai";
|
||||
import { Shine } from "../target/types/shine";
|
||||
|
||||
const MAGIC = Buffer.from("SHiNE", "utf8");
|
||||
const FORMAT_MAJOR = 1;
|
||||
const FORMAT_MINOR = 0;
|
||||
const RESERVED = Buffer.from([0, 0, 0, 0, 0]);
|
||||
const ZERO_HASH = Buffer.alloc(32, 0);
|
||||
const KEY_STATUS_CREATED = 0;
|
||||
|
||||
const LIMIT_STEP = 10_000n;
|
||||
const START_BONUS_LIMIT = 100_000n;
|
||||
const USERS_ECONOMY_CONFIG_SEED = "shine_users_economy_config";
|
||||
const SHINE_PAYMENTS_PROGRAM_ID = new PublicKey("m48pWRGWrMj3TEHjuU4zsp5Gju4e7ZaPovk8RcVt7kR");
|
||||
const SHINE_LOGIN_GUARD_PROGRAM_ID = new PublicKey("3xkopA7cXagxzMFrKdv3NCBfV6BKiRJCk69kr27M2sRo");
|
||||
const SHINE_PAYMENTS_INFLOW_VAULT_SEED = "shine_payments_inflow_vault";
|
||||
|
||||
type MutableFields = {
|
||||
blockchainKey: PublicKey;
|
||||
deviceKey: PublicKey;
|
||||
chainNumber: number;
|
||||
isServer: boolean;
|
||||
serverKey: PublicKey;
|
||||
serverAddress: string;
|
||||
syncServers: string[];
|
||||
accessServers: string[];
|
||||
trustedCount: number;
|
||||
};
|
||||
|
||||
type UnsignedRecord = {
|
||||
createdAtMs: bigint;
|
||||
updatedAtMs: bigint;
|
||||
version: number;
|
||||
prevHash: Buffer;
|
||||
login: string;
|
||||
rootKeyStatus: number;
|
||||
rootKey: PublicKey;
|
||||
blockchainKeyStatus: number;
|
||||
blockchainKey: PublicKey;
|
||||
deviceKeyStatus: number;
|
||||
deviceKey: PublicKey;
|
||||
chainNumber: number;
|
||||
balance: bigint;
|
||||
isServer: boolean;
|
||||
serverKey: PublicKey;
|
||||
serverAddress: string;
|
||||
syncServers: string[];
|
||||
accessServers: string[];
|
||||
trustedCount: number;
|
||||
};
|
||||
|
||||
function u16le(v: number): Buffer {
|
||||
const b = Buffer.alloc(2);
|
||||
b.writeUInt16LE(v, 0);
|
||||
return b;
|
||||
}
|
||||
|
||||
function u32le(v: number): Buffer {
|
||||
const b = Buffer.alloc(4);
|
||||
b.writeUInt32LE(v, 0);
|
||||
return b;
|
||||
}
|
||||
|
||||
function u64le(v: bigint): Buffer {
|
||||
const b = Buffer.alloc(8);
|
||||
b.writeBigUInt64LE(v, 0);
|
||||
return b;
|
||||
}
|
||||
|
||||
function serializeUnsignedRecord(r: UnsignedRecord): Buffer {
|
||||
const loginBytes = Buffer.from(r.login, "utf8");
|
||||
const serverAddressBytes = Buffer.from(r.serverAddress, "utf8");
|
||||
|
||||
const out: Buffer[] = [];
|
||||
out.push(MAGIC);
|
||||
out.push(Buffer.from([FORMAT_MAJOR]));
|
||||
out.push(Buffer.from([FORMAT_MINOR]));
|
||||
out.push(Buffer.alloc(2, 0)); // record_len placeholder
|
||||
|
||||
out.push(u64le(r.createdAtMs));
|
||||
out.push(u64le(r.updatedAtMs));
|
||||
out.push(u32le(r.version));
|
||||
out.push(r.prevHash);
|
||||
|
||||
out.push(Buffer.from([loginBytes.length]));
|
||||
out.push(loginBytes);
|
||||
|
||||
out.push(Buffer.from([r.rootKeyStatus]));
|
||||
out.push(r.rootKey.toBuffer());
|
||||
out.push(Buffer.from([r.blockchainKeyStatus]));
|
||||
out.push(r.blockchainKey.toBuffer());
|
||||
out.push(Buffer.from([r.deviceKeyStatus]));
|
||||
out.push(r.deviceKey.toBuffer());
|
||||
|
||||
out.push(u16le(r.chainNumber));
|
||||
out.push(u64le(r.balance));
|
||||
|
||||
out.push(Buffer.from([r.isServer ? 1 : 0]));
|
||||
if (r.isServer) {
|
||||
out.push(r.serverKey.toBuffer());
|
||||
out.push(Buffer.from([serverAddressBytes.length]));
|
||||
out.push(serverAddressBytes);
|
||||
out.push(Buffer.from([r.syncServers.length]));
|
||||
for (const s of r.syncServers) {
|
||||
const sb = Buffer.from(s, "utf8");
|
||||
out.push(Buffer.from([sb.length]));
|
||||
out.push(sb);
|
||||
}
|
||||
}
|
||||
|
||||
out.push(Buffer.from([r.accessServers.length]));
|
||||
for (const s of r.accessServers) {
|
||||
const sb = Buffer.from(s, "utf8");
|
||||
out.push(Buffer.from([sb.length]));
|
||||
out.push(sb);
|
||||
}
|
||||
|
||||
out.push(Buffer.from([r.trustedCount]));
|
||||
out.push(RESERVED);
|
||||
|
||||
const unsigned = Buffer.concat(out);
|
||||
const recordLen = unsigned.length + 64;
|
||||
unsigned.writeUInt16LE(recordLen, 7);
|
||||
return unsigned;
|
||||
}
|
||||
|
||||
function sha256(buf: Buffer): Buffer {
|
||||
return createHash("sha256").update(buf).digest();
|
||||
}
|
||||
|
||||
function extractSigFromEdIx(ixData: Buffer): Buffer {
|
||||
const signatureOffset = ixData.readUInt16LE(2);
|
||||
return ixData.subarray(signatureOffset, signatureOffset + 64);
|
||||
}
|
||||
|
||||
describe("shine_users e2e", () => {
|
||||
anchor.setProvider(anchor.AnchorProvider.env());
|
||||
const provider = anchor.getProvider() as anchor.AnchorProvider;
|
||||
const program = anchor.workspace.shine as Program<Shine>;
|
||||
|
||||
it("registers user and updates balance/server data", async () => {
|
||||
const login = `u${Date.now().toString().slice(-10)}`;
|
||||
const [userPda] = PublicKey.findProgramAddressSync(
|
||||
[Buffer.from("login="), Buffer.from(login, "utf8")],
|
||||
program.programId
|
||||
);
|
||||
const [usersEconomyConfigPda] = PublicKey.findProgramAddressSync(
|
||||
[Buffer.from(USERS_ECONOMY_CONFIG_SEED, "utf8")],
|
||||
program.programId
|
||||
);
|
||||
const [inflowVaultPda] = PublicKey.findProgramAddressSync(
|
||||
[Buffer.from(SHINE_PAYMENTS_INFLOW_VAULT_SEED, "utf8")],
|
||||
SHINE_PAYMENTS_PROGRAM_ID
|
||||
);
|
||||
|
||||
const economyAi = await provider.connection.getAccountInfo(usersEconomyConfigPda);
|
||||
if (!economyAi) {
|
||||
await program.methods
|
||||
.initUsersEconomyConfig()
|
||||
.accounts({
|
||||
signer: provider.wallet.publicKey,
|
||||
usersEconomyConfigPda,
|
||||
systemProgram: SystemProgram.programId,
|
||||
})
|
||||
.rpc();
|
||||
}
|
||||
|
||||
const root = anchor.web3.Keypair.generate();
|
||||
const blockchainKey = anchor.web3.Keypair.generate().publicKey;
|
||||
const deviceKey = anchor.web3.Keypair.generate().publicKey;
|
||||
const serverKey1 = anchor.web3.Keypair.generate().publicKey;
|
||||
const serverKey2 = anchor.web3.Keypair.generate().publicKey;
|
||||
|
||||
const createdAtMs = BigInt(Date.now());
|
||||
const additionalLimitCreate = 20_000n;
|
||||
expect(additionalLimitCreate % LIMIT_STEP).eq(0n);
|
||||
|
||||
const createRecord: UnsignedRecord = {
|
||||
createdAtMs,
|
||||
updatedAtMs: createdAtMs,
|
||||
version: 0,
|
||||
prevHash: ZERO_HASH,
|
||||
login,
|
||||
rootKeyStatus: KEY_STATUS_CREATED,
|
||||
rootKey: root.publicKey,
|
||||
blockchainKeyStatus: KEY_STATUS_CREATED,
|
||||
blockchainKey,
|
||||
deviceKeyStatus: KEY_STATUS_CREATED,
|
||||
deviceKey,
|
||||
chainNumber: 1,
|
||||
balance: START_BONUS_LIMIT + additionalLimitCreate,
|
||||
isServer: true,
|
||||
serverKey: serverKey1,
|
||||
serverAddress: "https://srv-1.local",
|
||||
syncServers: ["sync_srv_1", "sync_srv_2"],
|
||||
accessServers: ["access_srv_1"],
|
||||
trustedCount: 0,
|
||||
};
|
||||
|
||||
const createUnsigned = serializeUnsignedRecord(createRecord);
|
||||
const createHash = sha256(createUnsigned);
|
||||
const createEdIx = Ed25519Program.createInstructionWithPrivateKey({
|
||||
privateKey: root.secretKey,
|
||||
message: createHash,
|
||||
});
|
||||
const createSig = extractSigFromEdIx(Buffer.from(createEdIx.data));
|
||||
|
||||
const createIx = await program.methods
|
||||
.createUserPda({
|
||||
login,
|
||||
rootKey: root.publicKey,
|
||||
createdAtMs: new anchor.BN(createdAtMs.toString()),
|
||||
additionalLimit: new anchor.BN(additionalLimitCreate.toString()),
|
||||
fields: {
|
||||
blockchainKey,
|
||||
deviceKey,
|
||||
chainNumber: 1,
|
||||
isServer: true,
|
||||
serverKey: serverKey1,
|
||||
serverAddress: "https://srv-1.local",
|
||||
syncServers: ["sync_srv_1", "sync_srv_2"],
|
||||
accessServers: ["access_srv_1"],
|
||||
trustedCount: 0,
|
||||
},
|
||||
signature: createSig,
|
||||
})
|
||||
.accounts({
|
||||
signer: provider.wallet.publicKey,
|
||||
userPda,
|
||||
systemProgram: SystemProgram.programId,
|
||||
inflowVault: inflowVaultPda,
|
||||
instructions: SYSVAR_INSTRUCTIONS_PUBKEY,
|
||||
usersEconomyConfigPda,
|
||||
loginGuardProgram: SHINE_LOGIN_GUARD_PROGRAM_ID,
|
||||
})
|
||||
.instruction();
|
||||
|
||||
await provider.sendAndConfirm(new Transaction().add(createEdIx, createIx), []);
|
||||
|
||||
const createAcc = await provider.connection.getAccountInfo(userPda);
|
||||
expect(createAcc).not.eq(null);
|
||||
expect(createAcc!.owner.toBase58()).eq(program.programId.toBase58());
|
||||
|
||||
const additionalLimitUpdate = 30_000n;
|
||||
expect(additionalLimitUpdate % LIMIT_STEP).eq(0n);
|
||||
|
||||
const updateRecord: UnsignedRecord = {
|
||||
createdAtMs,
|
||||
updatedAtMs: createdAtMs + 1_000n,
|
||||
version: 1,
|
||||
prevHash: sha256(createUnsigned),
|
||||
login,
|
||||
rootKeyStatus: KEY_STATUS_CREATED,
|
||||
rootKey: root.publicKey,
|
||||
blockchainKeyStatus: KEY_STATUS_CREATED,
|
||||
blockchainKey: anchor.web3.Keypair.generate().publicKey,
|
||||
deviceKeyStatus: KEY_STATUS_CREATED,
|
||||
deviceKey: anchor.web3.Keypair.generate().publicKey,
|
||||
chainNumber: 1,
|
||||
balance: START_BONUS_LIMIT + additionalLimitCreate + additionalLimitUpdate,
|
||||
isServer: true,
|
||||
serverKey: serverKey2,
|
||||
serverAddress: "https://srv-2.local",
|
||||
syncServers: ["sync_srv_3"],
|
||||
accessServers: ["access_srv_2", "access_srv_3"],
|
||||
trustedCount: 0,
|
||||
};
|
||||
|
||||
const updateUnsigned = serializeUnsignedRecord(updateRecord);
|
||||
const updateHash = sha256(updateUnsigned);
|
||||
const updateEdIx = Ed25519Program.createInstructionWithPrivateKey({
|
||||
privateKey: root.secretKey,
|
||||
message: updateHash,
|
||||
});
|
||||
const updateSig = extractSigFromEdIx(Buffer.from(updateEdIx.data));
|
||||
|
||||
const updateIx = await program.methods
|
||||
.updateUserPda({
|
||||
login,
|
||||
rootKey: root.publicKey,
|
||||
createdAtMs: new anchor.BN(createdAtMs.toString()),
|
||||
updatedAtMs: new anchor.BN((createdAtMs + 1_000n).toString()),
|
||||
version: 1,
|
||||
prevHash: sha256(createUnsigned),
|
||||
additionalLimit: new anchor.BN(additionalLimitUpdate.toString()),
|
||||
fields: {
|
||||
blockchainKey: updateRecord.blockchainKey,
|
||||
deviceKey: updateRecord.deviceKey,
|
||||
chainNumber: 1,
|
||||
isServer: true,
|
||||
serverKey: serverKey2,
|
||||
serverAddress: "https://srv-2.local",
|
||||
syncServers: ["sync_srv_3"],
|
||||
accessServers: ["access_srv_2", "access_srv_3"],
|
||||
trustedCount: 0,
|
||||
},
|
||||
signature: updateSig,
|
||||
})
|
||||
.accounts({
|
||||
signer: provider.wallet.publicKey,
|
||||
userPda,
|
||||
systemProgram: SystemProgram.programId,
|
||||
inflowVault: inflowVaultPda,
|
||||
instructions: SYSVAR_INSTRUCTIONS_PUBKEY,
|
||||
usersEconomyConfigPda,
|
||||
})
|
||||
.instruction();
|
||||
|
||||
await provider.sendAndConfirm(new Transaction().add(updateEdIx, updateIx), []);
|
||||
|
||||
const updatedAcc = await provider.connection.getAccountInfo(userPda);
|
||||
expect(updatedAcc).not.eq(null);
|
||||
const data = updatedAcc!.data;
|
||||
expect(data.subarray(0, 5).toString("utf8")).eq("SHiNE");
|
||||
expect(data[5]).eq(FORMAT_MAJOR);
|
||||
expect(data[6]).eq(FORMAT_MINOR);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user