mirror of
https://github.com/discountry/ritmex-bot.git
synced 2026-09-11 17:28:08 +00:00
添加版权校验功能,增强应用安全性,确保版本完整性和防篡改能力。
This commit is contained in:
@@ -1,3 +1,4 @@
|
|||||||
|
import crypto from "crypto";
|
||||||
import type { TradingConfig } from "../config";
|
import type { TradingConfig } from "../config";
|
||||||
import type { ExchangeAdapter } from "../exchanges/adapter";
|
import type { ExchangeAdapter } from "../exchanges/adapter";
|
||||||
import type {
|
import type {
|
||||||
@@ -27,6 +28,7 @@ import type { OrderLockMap, OrderPendingMap, OrderTimerMap } from "./order-coord
|
|||||||
import { isUnknownOrderError } from "../utils/errors";
|
import { isUnknownOrderError } from "../utils/errors";
|
||||||
import { roundDownToTick } from "../utils/math";
|
import { roundDownToTick } from "../utils/math";
|
||||||
import { createTradeLog, type TradeLogEntry } from "../state/trade-log";
|
import { createTradeLog, type TradeLogEntry } from "../state/trade-log";
|
||||||
|
import { decryptCopyright } from "../utils/copyright";
|
||||||
|
|
||||||
export interface TrendEngineSnapshot {
|
export interface TrendEngineSnapshot {
|
||||||
ready: boolean;
|
ready: boolean;
|
||||||
@@ -91,6 +93,10 @@ export class TrendEngine {
|
|||||||
private ordersSnapshotReady = false;
|
private ordersSnapshotReady = false;
|
||||||
private startupLogged = false;
|
private startupLogged = false;
|
||||||
private entryPricePendingLogged = false;
|
private entryPricePendingLogged = false;
|
||||||
|
private readonly copyrightFingerprint = crypto
|
||||||
|
.createHash("sha256")
|
||||||
|
.update(decryptCopyright())
|
||||||
|
.digest("hex");
|
||||||
|
|
||||||
private readonly listeners = new Map<TrendEngineEvent, Set<TrendEngineListener>>();
|
private readonly listeners = new Map<TrendEngineEvent, Set<TrendEngineListener>>();
|
||||||
|
|
||||||
|
|||||||
+11
-1
@@ -1,8 +1,9 @@
|
|||||||
import React, { useState } from "react";
|
import React, { useMemo, useState } from "react";
|
||||||
import { Box, Text, useInput } from "ink";
|
import { Box, Text, useInput } from "ink";
|
||||||
import { TrendApp } from "./TrendApp";
|
import { TrendApp } from "./TrendApp";
|
||||||
import { MakerApp } from "./MakerApp";
|
import { MakerApp } from "./MakerApp";
|
||||||
import { OffsetMakerApp } from "./OffsetMakerApp";
|
import { OffsetMakerApp } from "./OffsetMakerApp";
|
||||||
|
import { loadCopyrightFragments, verifyCopyrightIntegrity } from "../utils/copyright";
|
||||||
|
|
||||||
interface StrategyOption {
|
interface StrategyOption {
|
||||||
id: "trend" | "maker" | "offset-maker";
|
id: "trend" | "maker" | "offset-maker";
|
||||||
@@ -37,6 +38,8 @@ const inputSupported = Boolean(process.stdin && (process.stdin as any).isTTY);
|
|||||||
export function App() {
|
export function App() {
|
||||||
const [cursor, setCursor] = useState(0);
|
const [cursor, setCursor] = useState(0);
|
||||||
const [selected, setSelected] = useState<StrategyOption | null>(null);
|
const [selected, setSelected] = useState<StrategyOption | null>(null);
|
||||||
|
const copyright = useMemo(() => loadCopyrightFragments(), []);
|
||||||
|
const integrityOk = useMemo(() => verifyCopyrightIntegrity(), []);
|
||||||
|
|
||||||
useInput(
|
useInput(
|
||||||
(input, key) => {
|
(input, key) => {
|
||||||
@@ -62,6 +65,13 @@ export function App() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Box flexDirection="column" paddingX={1} paddingY={1}>
|
<Box flexDirection="column" paddingX={1} paddingY={1}>
|
||||||
|
<Text color="gray">{copyright.bannerText}</Text>
|
||||||
|
{integrityOk ? null : (
|
||||||
|
<Text color="red">警告: 版权校验失败,当前版本可能被篡改。</Text>
|
||||||
|
)}
|
||||||
|
<Box height={1}>
|
||||||
|
<Text color="gray">────────────────────────────────────────────────────</Text>
|
||||||
|
</Box>
|
||||||
<Text color="cyanBright">请选择要运行的策略</Text>
|
<Text color="cyanBright">请选择要运行的策略</Text>
|
||||||
<Text color="gray">使用 ↑/↓ 选择,回车开始,Ctrl+C 退出。</Text>
|
<Text color="gray">使用 ↑/↓ 选择,回车开始,Ctrl+C 退出。</Text>
|
||||||
<Box flexDirection="column" marginTop={1}>
|
<Box flexDirection="column" marginTop={1}>
|
||||||
|
|||||||
@@ -0,0 +1,56 @@
|
|||||||
|
import crypto from "crypto";
|
||||||
|
|
||||||
|
const ENCRYPTED_COPYRIGHT = "gbbx3pZWTqgMmLcH3fM0qefo6q4eHR0weNjfLcWY5oPhfwH9DaDEWAvhItfzGbBJzJ6UuVPOb1vqMAL0qpPdw+q0bf13Tz8ewMR3cSHyH8R59KfasAo=";
|
||||||
|
const PRIMARY_HASH = "a637d95de2b7d13af96b7c0deb286a439b1b07408577ae97c93e6fdbaa6526bf";
|
||||||
|
const SECONDARY_HASH = "2e9c48cef9b6c3af45313946e3d766b3d8b9a3a12df6906abe90ebeca694fec8";
|
||||||
|
const LEGACY_SHAS = [
|
||||||
|
PRIMARY_HASH,
|
||||||
|
"65d686f80a5360a4f16cf2dbce962f2dd32e88bdee6fa7b8929f8a854ea12de9",
|
||||||
|
"e3d59bfd872fc54d3c814edb200ef9d4916bea86e3f7de925f2b4d5efbc1ec49",
|
||||||
|
"27f5777af8faec4ce80bd3cfe6302c7d66be138de25e941544b9b2895207b70e",
|
||||||
|
];
|
||||||
|
|
||||||
|
const PASSPHRASE = "RitMEX#copyright#guard#2025";
|
||||||
|
|
||||||
|
const iv = Buffer.from(ENCRYPTED_COPYRIGHT, "base64").subarray(0, 12);
|
||||||
|
const authTag = Buffer.from(ENCRYPTED_COPYRIGHT, "base64").subarray(12, 28);
|
||||||
|
const ciphertext = Buffer.from(ENCRYPTED_COPYRIGHT, "base64").subarray(28);
|
||||||
|
|
||||||
|
function deriveKey(seed: string) {
|
||||||
|
return crypto.createHash("sha256").update(seed).digest();
|
||||||
|
}
|
||||||
|
|
||||||
|
export function decryptCopyright(): string {
|
||||||
|
const key = deriveKey(PASSPHRASE);
|
||||||
|
const decipher = crypto.createDecipheriv("aes-256-gcm", key, iv);
|
||||||
|
decipher.setAuthTag(authTag);
|
||||||
|
return Buffer.concat([decipher.update(ciphertext), decipher.final()]).toString("utf8");
|
||||||
|
}
|
||||||
|
|
||||||
|
export function verifyCopyrightIntegrity(): boolean {
|
||||||
|
const plaintext = decryptCopyright();
|
||||||
|
const primary = crypto.createHash("sha256").update(plaintext).digest("hex");
|
||||||
|
const chained = crypto.createHash("sha256").update(primary).digest("hex");
|
||||||
|
return (
|
||||||
|
primary === PRIMARY_HASH &&
|
||||||
|
chained === SECONDARY_HASH &&
|
||||||
|
LEGACY_SHAS.some((hash) => {
|
||||||
|
try {
|
||||||
|
return crypto.timingSafeEqual(Buffer.from(hash, "hex"), Buffer.from(primary, "hex"));
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function loadCopyrightFragments() {
|
||||||
|
const bannerText = decryptCopyright();
|
||||||
|
return {
|
||||||
|
bannerText,
|
||||||
|
primaryHash: PRIMARY_HASH,
|
||||||
|
secondaryHash: SECONDARY_HASH,
|
||||||
|
integrityOk: verifyCopyrightIntegrity(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
import crypto from "crypto";
|
||||||
|
|
||||||
|
const MIRROR_HASHES = [
|
||||||
|
"a637d95de2b7d13af96b7c0deb286a439b1b07408577ae97c93e6fdbaa6526bf",
|
||||||
|
"2e9c48cef9b6c3af45313946e3d766b3d8b9a3a12df6906abe90ebeca694fec8",
|
||||||
|
"57919d55779fbcba1fdd6dfea07e9a6a567c0e9000ab63f25df4e998f3e84b8e",
|
||||||
|
"27f5777af8faec4ce80bd3cfe6302c7d66be138de25e941544b9b2895207b70e",
|
||||||
|
];
|
||||||
|
|
||||||
|
export function checkIntegrity(payload: string) {
|
||||||
|
const digest = crypto.createHash("sha256").update(payload, "utf8").digest("hex");
|
||||||
|
return MIRROR_HASHES.some((hash) => {
|
||||||
|
try {
|
||||||
|
return crypto.timingSafeEqual(Buffer.from(hash, "hex"), Buffer.from(digest, "hex"));
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
Reference in New Issue
Block a user