import { DomainError } from '@/shared/exceptions/domain.exception'; import { createHash, createCipheriv, createDecipheriv, randomBytes, scryptSync } from 'crypto'; import * as bip39 from '@scure/bip39'; import { wordlist } from '@scure/bip39/wordlists/english'; // ============ UserId ============ export class UserId { constructor(public readonly value: bigint) { // 允许 0 作为临时值(表示未持久化的新账户) if (value === null || value === undefined) { throw new DomainError('UserId不能为空'); } } static create(value: bigint | string | number): UserId { if (typeof value === 'string') { return new UserId(BigInt(value)); } if (typeof value === 'number') { return new UserId(BigInt(value)); } return new UserId(value); } equals(other: UserId): boolean { return this.value === other.value; } toString(): string { return this.value.toString(); } } // ============ AccountSequence ============ export class AccountSequence { constructor(public readonly value: number) { if (value <= 0) throw new DomainError('账户序列号必须大于0'); } static create(value: number): AccountSequence { return new AccountSequence(value); } static next(current: AccountSequence): AccountSequence { return new AccountSequence(current.value + 1); } equals(other: AccountSequence): boolean { return this.value === other.value; } } // ============ PhoneNumber ============ export class PhoneNumber { constructor(public readonly value: string) { if (!/^1[3-9]\d{9}$/.test(value)) { throw new DomainError('手机号格式错误'); } } static create(value: string): PhoneNumber { return new PhoneNumber(value); } equals(other: PhoneNumber): boolean { return this.value === other.value; } masked(): string { return this.value.replace(/(\d{3})\d{4}(\d{4})/, '$1****$2'); } } // ============ ReferralCode ============ export class ReferralCode { constructor(public readonly value: string) { if (!/^[A-Z0-9]{6}$/.test(value)) { throw new DomainError('推荐码格式错误'); } } static generate(): ReferralCode { const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; let code = ''; for (let i = 0; i < 6; i++) { code += chars.charAt(Math.floor(Math.random() * chars.length)); } return new ReferralCode(code); } static create(value: string): ReferralCode { return new ReferralCode(value.toUpperCase()); } equals(other: ReferralCode): boolean { return this.value === other.value; } } // ============ ProvinceCode & CityCode ============ export class ProvinceCode { constructor(public readonly value: string) {} static create(value: string): ProvinceCode { return new ProvinceCode(value || 'DEFAULT'); } } export class CityCode { constructor(public readonly value: string) {} static create(value: string): CityCode { return new CityCode(value || 'DEFAULT'); } } // ============ Mnemonic ============ export class Mnemonic { constructor(public readonly value: string) { if (!bip39.validateMnemonic(value, wordlist)) { throw new DomainError('助记词格式错误'); } } static generate(): Mnemonic { const mnemonic = bip39.generateMnemonic(wordlist, 128); return new Mnemonic(mnemonic); } static create(value: string): Mnemonic { return new Mnemonic(value); } toSeed(): Uint8Array { return bip39.mnemonicToSeedSync(this.value); } getWords(): string[] { return this.value.split(' '); } equals(other: Mnemonic): boolean { return this.value === other.value; } } // ============ HardwareInfo ============ export interface HardwareInfo { platform?: string; // ios, android, web deviceModel?: string; // iPhone 15 Pro, Pixel 8 osVersion?: string; // iOS 17.2, Android 14 appVersion?: string; // 1.0.0 screenWidth?: number; screenHeight?: number; locale?: string; // zh-CN, en-US timezone?: string; // Asia/Shanghai } // ============ DeviceInfo ============ export class DeviceInfo { private _lastActiveAt: Date; private _hardwareInfo: HardwareInfo; constructor( public readonly deviceId: string, public readonly deviceName: string, public readonly addedAt: Date, lastActiveAt: Date, hardwareInfo?: HardwareInfo, ) { this._lastActiveAt = lastActiveAt; this._hardwareInfo = hardwareInfo || {}; } get lastActiveAt(): Date { return this._lastActiveAt; } get hardwareInfo(): HardwareInfo { return this._hardwareInfo; } get platform(): string | undefined { return this._hardwareInfo.platform; } get deviceModel(): string | undefined { return this._hardwareInfo.deviceModel; } get osVersion(): string | undefined { return this._hardwareInfo.osVersion; } get appVersion(): string | undefined { return this._hardwareInfo.appVersion; } updateActivity(): void { this._lastActiveAt = new Date(); } updateHardwareInfo(info: HardwareInfo): void { this._hardwareInfo = { ...this._hardwareInfo, ...info }; } } // ============ ChainType ============ export enum ChainType { KAVA = 'KAVA', DST = 'DST', BSC = 'BSC', } export const CHAIN_CONFIG = { [ChainType.KAVA]: { prefix: 'kava', derivationPath: "m/44'/459'/0'/0/0" }, [ChainType.DST]: { prefix: 'dst', derivationPath: "m/44'/118'/0'/0/0" }, [ChainType.BSC]: { prefix: '0x', derivationPath: "m/44'/60'/0'/0/0" }, }; // ============ KYCInfo ============ export class KYCInfo { constructor( public readonly realName: string, public readonly idCardNumber: string, public readonly idCardFrontUrl: string, public readonly idCardBackUrl: string, ) { if (!realName || realName.length < 2) { throw new DomainError('真实姓名不合法'); } if (!/^[1-9]\d{5}(18|19|20)\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\d|3[01])\d{3}[0-9Xx]$/.test(idCardNumber)) { throw new DomainError('身份证号格式错误'); } } static create(params: { realName: string; idCardNumber: string; idCardFrontUrl: string; idCardBackUrl: string }): KYCInfo { return new KYCInfo(params.realName, params.idCardNumber, params.idCardFrontUrl, params.idCardBackUrl); } maskedIdCardNumber(): string { return this.idCardNumber.replace(/(\d{6})\d{8}(\d{4})/, '$1********$2'); } } // ============ Enums ============ export enum KYCStatus { NOT_VERIFIED = 'NOT_VERIFIED', PENDING = 'PENDING', VERIFIED = 'VERIFIED', REJECTED = 'REJECTED', } export enum AccountStatus { ACTIVE = 'ACTIVE', FROZEN = 'FROZEN', DEACTIVATED = 'DEACTIVATED', } export enum AddressStatus { ACTIVE = 'ACTIVE', DISABLED = 'DISABLED', } // ============ AddressId ============ export class AddressId { constructor(public readonly value: string) {} static generate(): AddressId { return new AddressId(crypto.randomUUID()); } static create(value: string): AddressId { return new AddressId(value); } } // ============ MnemonicEncryption ============ export class MnemonicEncryption { static encrypt(mnemonic: string, key: string): string { const derivedKey = this.deriveKey(key); const iv = randomBytes(16); const cipher = createCipheriv('aes-256-gcm', derivedKey, iv); let encrypted = cipher.update(mnemonic, 'utf8', 'hex'); encrypted += cipher.final('hex'); const authTag = cipher.getAuthTag(); return JSON.stringify({ encrypted, authTag: authTag.toString('hex'), iv: iv.toString('hex'), }); } static decrypt(encryptedData: string, key: string): string { const { encrypted, authTag, iv } = JSON.parse(encryptedData); const derivedKey = this.deriveKey(key); const decipher = createDecipheriv('aes-256-gcm', derivedKey, Buffer.from(iv, 'hex')); decipher.setAuthTag(Buffer.from(authTag, 'hex')); let decrypted = decipher.update(encrypted, 'hex', 'utf8'); decrypted += decipher.final('utf8'); return decrypted; } private static deriveKey(password: string): Buffer { return scryptSync(password, 'rwa-wallet-salt', 32); } }