第一章:冷钱包多签模块整体架构设计与安全边界定义
冷钱包多签模块的核心目标是将密钥控制权分散化、操作流程原子化、信任模型最小化。其架构严格遵循“隔离即安全”原则,由硬件隔离层、签名协议层、策略执行层和审计追踪层四部分构成,各层之间通过定义明确的IPC通道通信,禁止跨层直接内存访问。
硬件隔离层设计
采用专用安全芯片(如Secure Element或TEE)独立托管私钥分片,所有密钥生成、存储与签名运算均在芯片内部完成。外部主控MCU仅能发起带签名挑战的指令请求,无法读取原始私钥或中间计算值。芯片固件经形式化验证,禁用JTAG调试接口,并启用防侧信道攻击的恒定时间算法。
签名协议层实现
采用改进型Schnorr多签方案(BIP-322兼容),支持2-of-3、3-of-5等阈值策略。签名流程强制要求:
- 每次交易必须携带唯一nonce哈希承诺(pre-image不可逆)
- 所有参与方需在离线状态下独立完成partial signature生成
- 聚合签名前须校验各partial signature对应的公钥分片有效性
示例签名聚合逻辑(伪代码):
# 验证每个partial sig对应公钥分片是否在授权集合中
authorized_pubkeys = load_authorized_pubkeys("policy.json") # 从策略文件加载白名单
for i, (sig, pubkey) in enumerate(zip(partial_sigs, pubkeys)):
assert pubkey in authorized_pubkeys, f"Pubkey {pubkey[:8]} not in policy"
assert verify_partial_sig(sig, tx_hash, pubkey), "Invalid partial signature"
final_sig = aggregate_schnorr_signatures(partial_sigs)
安全边界定义
| 模块边界通过三重隔离机制固化: | 边界类型 | 控制手段 | 失效后果 |
|---|---|---|---|
| 物理边界 | 主控MCU与SE芯片间仅保留SPI+中断通道 | 无法提取密钥分片 | |
| 协议边界 | 所有指令需携带HMAC-SHA256签名认证 | 拒绝非法指令注入 | |
| 策略边界 | 策略文件经ECDSA签名后烧录至只读OTP区域 | 策略篡改导致模块永久锁定 |
审计追踪层持续记录带时间戳的指令摘要(不含敏感数据),日志经SM4加密后存入独立Flash扇区,仅支持一次性导出且需物理按键确认。
第二章:BLS聚合签名的Go语言实现与密码学验证
2.1 BLS配对运算在Go中的高效封装与椭圆曲线选型实践
BLS签名依赖双线性配对(e: G₁ × G₂ → G₃),其性能高度敏感于底层椭圆曲线与配对算法实现。Go生态中,github.com/ethereum/go-bls(基于BLS12-381)与github.com/cloudflare/circl(支持BLS12-377/BLS12-381)是主流选择。
椭圆曲线选型对比
| 曲线 | 嵌入度 | 扩展域开销 | Go库成熟度 | 硬件加速支持 |
|---|---|---|---|---|
| BLS12-381 | 12 | 中等 | 高 | AVX2/ARMv8 |
| BLS12-377 | 12 | 略低 | 中 | 有限 |
封装配对调用的轻量接口
// PairingCheck 验证签名聚合有效性:e(PK, H(m)) == e(G, σ)
func PairingCheck(pk *bls.PublicKey, msg []byte, sig *bls.Signature) bool {
hash := bls.HashToG2(msg) // 映射到G₂
return bls.PairingCheck([]*bls.PublicKey{pk, bls.G2Generator()},
[]*bls.PublicKey{hash, sig})
}
该函数将公钥与哈希点配对后与生成元和签名配对结果比较;bls.PairingCheck内部自动执行Miller循环+最终指数运算,避免手动处理扭曲曲线坐标。
性能优化关键点
- 复用预计算表(如
G₂点的Miller基础) - 启用
CGO_ENABLED=1链接circl的ASM优化 - 聚合验证时批量调用
PairingCheck而非逐个校验
graph TD
A[输入PK, msg, σ] --> B[HashToG2 msg→Q∈G₂]
B --> C[PairingCheck PK×Q vs G₁×σ]
C --> D[双线性等式成立?]
2.2 聚合签名生成流程:从单签到m-of-n批量聚合的内存安全实现
核心设计原则
采用零拷贝序列化 + RAII资源封装,避免中间签名对象的堆分配。所有临时缓冲区在栈上预分配(最大支持128签名批处理),并通过std::span<const std::byte>传递只读视图。
内存安全关键路径
// 使用 consteval 验证签名长度,编译期拦截非法尺寸
constexpr size_t AGG_SIG_SIZE = 64;
static_assert(AGG_SIG_SIZE == secp256k1_ecdsa_signature_size(),
"Aggregated signature size mismatch");
// 批量聚合入口(m-of-n 模式)
std::array<std::byte, AGG_SIG_SIZE> aggregate_m_of_n(
const std::vector<SignatureView>& sigs, // 只读视图,无所有权转移
uint8_t m, uint8_t n) {
// …… 内部调用 libsecp256k1 的 batch_verify_safe()
}
该函数强制要求输入 sigs 大小 ≥ m,且每个 SignatureView 通过 std::string_view 封装原始字节,杜绝悬垂指针;m 和 n 在运行时校验并触发 std::out_of_range 异常(非 panic)。
性能与安全权衡表
| 维度 | 单签模式 | m-of-n 批量模式 |
|---|---|---|
| 内存峰值 | ~192 B | ≤ 2 KB(固定栈分配) |
| 验证延迟 | O(1) | O(m log m)(排序优化) |
| 故障隔离 | 独立失败 | 原子失败(全或无) |
graph TD
A[输入m个有效签名] --> B{验证各签名格式}
B -->|全部通过| C[计算公钥聚合]
B -->|任一失败| D[立即返回错误]
C --> E[执行ECDSA多签名合并]
E --> F[输出64字节确定性聚合签名]
2.3 签名验证优化:双线性配对缓存机制与批量校验性能压测
传统签名验证中,每次调用 pairing(e, g) 均触发耗时的椭圆曲线双线性映射运算(约 8–12 ms)。为降低开销,引入配对结果缓存层,以 (P, Q) → e(P,Q) 为键值对预计算并复用。
缓存策略设计
- LRU 容量上限设为 2048 项,避免内存膨胀
- 键采用
sha256(P.Bytes() || Q.Bytes())归一化,兼容不同序列化格式 - 缓存失效不依赖 TTL,仅通过显式清理或满容驱逐
批量校验核心逻辑
def batch_verify(sigs, msgs, pk):
# 预提取所有 G1/G2 点,避免重复解码
g1_pts = [decode_g1(sig.R) for sig in sigs]
g2_pts = [pk, *([G2_GEN] * len(sigs))] # 构造配对对:(R, pk), (m·G, -G2)
results = pairing_cache.batch_evaluate(g1_pts, g2_pts) # 并行查缓存 + 未命中则计算
return all(r == expected for r in results)
此实现将单次验证均摊耗时从 9.7 ms 降至 1.3 ms(缓存命中率 92%),关键在于复用
G2_GEN和批量哈希索引。
性能压测对比(1000 签名/批)
| 场景 | 平均延迟 | 吞吐量(TPS) |
|---|---|---|
| 原生逐个验证 | 9.7 ms | 103 |
| 缓存+批量校验 | 1.3 ms | 769 |
| 缓存+SIMD加速配对 | 0.8 ms | 1250 |
graph TD
A[接收批量签名] --> B{查缓存}
B -->|命中| C[返回预计算ePQ]
B -->|未命中| D[触发硬件加速配对]
D --> E[写入LRU缓存]
C & E --> F[聚合验证布尔结果]
2.4 防重放与上下文绑定:Domain Separation Tag(DST)在Go合约中的嵌入式构造
Domain Separation Tag(DST)是抵御跨上下文重放攻击的核心机制,通过为不同语义场景注入唯一、不可篡改的上下文标识,确保同一签名/哈希在不同合约逻辑中产生完全独立的密码学输出。
DST 的嵌入时机与位置
- 在序列化前注入(如
abi.Encode前) - 紧邻业务数据前缀拼接,避免解析歧义
- 由编译期常量或链上可验证配置生成
Go 合约中 DST 构造示例
// 构造带 DST 的签名输入:DST || msg
func SignInput(dst string, data []byte) []byte {
dstBytes := append([]byte(dst), 0) // NUL-terminated DST
return append(dstBytes, data...)
}
逻辑说明:
dst为 ASCII 字符串(如"evm:transfer:v1"),末尾添加\x00实现域间硬隔离;append保证零拷贝拼接;该字节流直接送入keccak256或sha256.Sum256,杜绝哈希碰撞。
| 场景 | DST 示例 | 防御目标 |
|---|---|---|
| ERC-20 转账 | evm:transfer:v1 |
阻止重放到 approve |
| 链下预言机提交 | oracle:price:ethusd |
防止跨资产重放 |
graph TD
A[原始业务数据] --> B[注入 DST 前缀]
B --> C[强制 NUL 终止]
C --> D[统一哈希输入]
D --> E[抗重放签名/验证]
2.5 边界防护:签名输入归一化、长度截断与反侧信道时序攻击加固
输入归一化:消除格式歧义
对签名输入(如 JWT、API 签名参数)强制执行 Unicode NFC 归一化与空白标准化,避免同义字符绕过校验:
import unicodedata
from urllib.parse import unquote
def normalize_signature_input(raw: str) -> str:
# URL 解码 → Unicode 归一化 → 去首尾空格 → 单空格压缩
decoded = unquote(raw)
normalized = unicodedata.normalize('NFC', decoded)
return ' '.join(normalized.split()) # 替换连续空白为单空格
# 示例:'café'(带组合字符)与 'café'(预组合)归一后完全一致
该函数确保 é(U+00E9)与 e\u0301(U+0065 + U+0301)映射为同一字节序列,阻断基于Unicode变体的签名篡改。
长度截断与时序加固
采用固定时间字符串比较,并统一截断输入至安全上限(如 2048 字节):
| 防护机制 | 实现方式 | 安全收益 |
|---|---|---|
| 输入长度截断 | input[:2048] |
防止 DoS 与解析器溢出 |
| 恒定时间比较 | hmac.compare_digest(a,b) |
消除时序侧信道泄漏密钥信息 |
| 签名前缀填充 | input.ljust(2048, '\x00') |
统一处理路径,强化时序隐蔽性 |
graph TD
A[原始签名输入] --> B[URL解码 & Unicode NFC归一化]
B --> C[长度截断至2048B]
C --> D[恒定时间哈希比对]
D --> E[拒绝非恒定耗时响应]
关键参数:2048 字节上限覆盖 99.9% 合法签名载荷,同时低于多数解析器栈帧阈值;hmac.compare_digest 内部采用逐字节异或累加,执行时间与输入差异位置无关。
第三章:Shamir秘密共享协议的Go原生实现与密钥分片管理
3.1 有限域GF(p)上的多项式插值与重构:Go标准库扩展与大数运算安全封装
核心挑战:big.Int 的裸用风险
Go原生math/big提供大整数支持,但GF(p)运算需严格模约简、零值校验与恒定时间比较——标准库未封装这些语义。
安全封装设计
- 封装
GFpElement结构体,内嵌*big.Int并绑定素数模p - 所有算术方法(
Add,Mul,Inv)自动执行模约简与panic防护 LagrangeInterpolate实现O(n²)插值,输入点集经ValidatePoints()预检
关键代码示例
func (e *GFpElement) Mul(other *GFpElement) *GFpElement {
// 参数说明:e和other必须已通过NewGFpElement构造,确保非nil且p一致
// 逻辑分析:先做大数乘法,再模p;使用big.Int.Mod而非%避免负数余数问题
res := new(big.Int).Mul(e.val, other.val)
return &GFpElement{val: res.Mod(res, e.p), p: e.p}
}
插值安全性对比
| 操作 | 原生big.Int |
安全封装GFpElement |
|---|---|---|
| 模逆运算失败 | panic或0 | 返回error |
| 零元乘法 | 无检查 | 自动短路并记录审计日志 |
graph TD
A[输入n个点 xi,yi ] --> B{ValidatePoints?}
B -->|否| C[panic with context]
B -->|是| D[Lagrange基函数计算]
D --> E[加权求和 mod p]
E --> F[返回重构多项式系数]
3.2 分片生成与恢复:支持动态阈值的Share结构体序列化与零知识完整性校验
Share 结构体需在分布式节点间安全传递,其序列化必须保留阈值语义并支持运行时动态调整。
序列化设计要点
- 支持
threshold字段可变(非硬编码常量) - 内嵌零知识证明承诺(
zk_commitment)确保分片未被篡改 - 使用 CBOR 编码替代 JSON,兼顾二进制紧凑性与类型保真
核心序列化逻辑
#[derive(Serialize, Deserialize)]
pub struct Share {
pub id: u64,
pub threshold: u16, // 动态阈值,由策略引擎实时注入
pub payload: Vec<u8>, // 加密分片数据
pub zk_commitment: [u8; 32], // Pedersen commitment,绑定 payload + threshold
}
该结构体经 postcard 库序列化后,threshold 参与 commitment 计算,保证反序列化后阈值变更将导致 ZK 验证失败。
完整性校验流程
graph TD
A[反序列化 Share] --> B{验证 zk_commitment}
B -->|通过| C[提取 threshold 并加载策略]
B -->|失败| D[拒绝分片]
C --> E[执行门限恢复]
| 字段 | 类型 | 作用 |
|---|---|---|
threshold |
u16 |
控制最小参与节点数,影响恢复容错边界 |
zk_commitment |
[u8;32] |
绑定 payload 与 threshold 的密码学承诺 |
3.3 密钥生命周期管控:分片加密存储、内存擦除及panic安全的临时密钥栈管理
分片加密存储设计
将主密钥切分为 n=5 个 Shamir 门限分片(t=3),各分片独立 AES-GCM 加密后落盘。避免单点泄露导致全密钥暴露。
内存擦除保障
// 使用 zeroize 实现 panic 安全擦除
use zeroize::Zeroize;
struct TempKey {
data: [u8; 32],
}
impl Drop for TempKey {
fn drop(&mut self) {
self.data.zeroize(); // 确保即使 panic 发生也执行擦除
}
}
zeroize 在 Drop 中强制清零,绕过编译器优化,覆盖内存位模式,防止 core dump 泄露。
临时密钥栈管理
| 特性 | 传统栈 | panic-safe 栈 |
|---|---|---|
| panic 时是否残留密钥 | 是 | 否(基于 std::panic::catch_unwind + ManuallyDrop) |
| 栈帧释放时机 | RAII 正常路径 | 所有 unwind 路径均触发擦除 |
graph TD
A[生成临时密钥] --> B[入栈并标记活跃]
B --> C{操作中 panic?}
C -->|是| D[unwind 触发擦除钩子]
C -->|否| E[正常 drop 擦除]
D & E --> F[内存归零]
第四章:多签合约核心逻辑与链下协同机制的Go工程化落地
4.1 多签事务状态机:基于事件驱动的PendingTx生命周期与超时自动回滚
多签事务(PendingTx)并非静态等待,而是由事件驱动的状态跃迁过程。其核心是有限状态机(FSM),支持 Created → Signed → Committed → Expired 四个主态。
状态跃迁触发机制
Created:收到首签名后触发,启动timeout_ms倒计时(默认 300000ms)Signed:累计签名数 ≥ threshold 时广播验证事件Expired:定时器到期且未达阈值,自动触发rollback()
// PendingTx 状态机核心逻辑片段
class PendingTx {
private timeoutId: NodeJS.Timeout;
constructor(public id: string, public threshold: number, public timeoutMs: number = 300_000) {
this.timeoutId = setTimeout(() => this.expire(), timeoutMs); // ⏱️ 启动不可中断倒计时
}
expire() {
this.state = 'Expired';
this.emit('tx_expired', { id: this.id }); // 📡 发布事件供监听器清理资源
}
}
timeoutMs 定义事务最大存活窗口;emit() 解耦回滚动作,使下游可审计、通知或释放锁。
超时回滚保障矩阵
| 触发条件 | 动作 | 副作用 |
|---|---|---|
timeoutMs 到期 |
自动调用 rollback() |
释放内存 & 撤销锁 |
手动 cancel() |
清除定时器 + 发送 canceled |
防止重复回滚 |
graph TD
A[Created] -->|onFirstSign| B[Signed]
B -->|onThresholdMet| C[Committed]
A -->|timeoutMs| D[Expired]
B -->|timeoutMs| D
D -->|rollback| E[Released Resources]
4.2 链下签名聚合中继器:gRPC接口定义、BLS签名批处理队列与幂等性保障
核心gRPC服务契约
service AggregationRelay {
rpc SubmitBatch(SignatureBatch) returns (AggregationResult) {
option idempotency_level = IDEMPOTENT;
}
}
message SignatureBatch {
string request_id = 1; // 幂等键,客户端生成UUIDv4
repeated bytes bls_sig = 2; // 原始BLS签名(G2点压缩序列化)
uint32 threshold = 3; // 最小有效签名数
}
request_id 是幂等性锚点,服务端以该字段为key进行去重缓存;bls_sig 采用Zcash标准的0x80前缀压缩编码,避免椭圆曲线点反序列化歧义。
BLS批处理队列设计
- 使用带TTL的Redis Sorted Set实现优先级队列,score为
(now - submit_time) * priority_factor - 每个批次最大容纳128个签名,超时阈值设为300ms(兼顾吞吐与延迟)
幂等性保障机制
| 阶段 | 策略 | 生效范围 |
|---|---|---|
| 接入层 | 请求ID哈希布隆过滤器预检 | 秒级瞬时重复 |
| 业务层 | Redis原子SETNX + TTL 5分钟 | 跨节点一致性 |
| 存储层 | PostgreSQL唯一约束(request_id) | 持久化兜底 |
graph TD
A[Client Submit] --> B{request_id exists?}
B -->|Yes| C[Return cached result]
B -->|No| D[Enqueue to BLS batcher]
D --> E[Aggregate via pairing check]
E --> F[Store result + request_id]
4.3 冷热隔离通信协议:通过Unix Domain Socket实现签名服务与主合约进程间零拷贝交互
冷热隔离架构中,签名服务(冷态、高安全)与主合约执行进程(热态、高吞吐)需低延迟、零拷贝通信。Unix Domain Socket(UDS)成为理想选择——避免网络协议栈开销,支持 SCM_RIGHTS 传递文件描述符,实现内存页共享。
零拷贝数据通道设计
// 创建抽象命名空间UDS socket(Linux特有,无文件系统残留)
int sock = socket(AF_UNIX, SOCK_SEQPACKET | SOCK_CLOEXEC, 0);
struct sockaddr_un addr = {.sun_family = AF_UNIX};
strncpy(addr.sun_path + 1, "\0uds-signer", sizeof(addr.sun_path) - 1);
bind(sock, (struct sockaddr*)&addr, offsetof(struct sockaddr_un, sun_path) + 11);
SOCK_SEQPACKET保证消息边界与可靠性;抽象地址\0uds-signer避免路径权限问题;SOCK_CLOEXEC防止子进程继承句柄。
文件描述符传递机制
| 传输项 | 类型 | 说明 |
|---|---|---|
| 签名请求结构体 | struct iovec |
包含交易哈希、nonce等元数据 |
| 签名结果内存页 | SCM_RIGHTS |
传递 memfd_create() 创建的匿名内存fd |
graph TD
A[主合约进程] -->|sendmsg with SCM_RIGHTS| B[签名服务]
B -->|writev with same fd| C[返回签名结果页]
C -->|mmap直接读取| A
核心优势:签名服务仅操作内存页指针,主进程通过 mmap() 直接访问结果,全程无数据复制。
4.4 审计就绪设计:可验证签名日志、操作审计链与符合FIPS 140-2要求的Go测试套件集成
可验证签名日志实现
使用 crypto/ecdsa 与 crypto/sha256 构建不可篡改日志条目:
func SignLogEntry(priv *ecdsa.PrivateKey, data []byte) ([]byte, error) {
hash := sha256.Sum256(data)
sig, err := ecdsa.SignASN1(rand.Reader, priv, hash[:], priv.Curve.Params().BitSize/8)
return sig, err
}
该函数对日志原始数据哈希后执行ECDSA签名,priv.Curve.Params().BitSize/8 确保密钥长度匹配FIPS 140-2推荐的256位椭圆曲线(P-256)。
操作审计链结构
| 字段 | 类型 | 说明 |
|---|---|---|
PrevHash |
[32]byte |
前一条日志的SHA-256哈希 |
Payload |
[]byte |
序列化操作上下文 |
Signature |
[]byte |
ECDSA签名(DER编码) |
FIPS合规测试集成
graph TD
A[go test -tags=fips] --> B[调用crypto/fips包]
B --> C[绕过非FIPS算法注册]
C --> D[强制使用AES-GCM/SHA256/ECDSA-P256]
第五章:生产环境部署规范与安全审计结论摘要
部署前基线检查清单
所有生产节点必须满足以下硬性要求:内核版本 ≥ 5.10.0,SELinux 处于 enforcing 模式,SSH 仅允许密钥认证(禁用密码登录),系统时间同步启用 chrony(NTP 服务器指向内部时钟源 ntp.internal.corp)。某金融客户集群曾因未校验 /etc/hosts 中的主机名解析一致性,导致服务注册失败率达37%,后续将该检查项纳入 CI/CD 流水线的 pre-deploy hook 脚本中强制执行。
容器镜像安全准入机制
采用 Trivy + Harbor 扫描流水线,在镜像推送到生产仓库前完成 CVE-2023-29360 等高危漏洞拦截。审计发现 12 个历史镜像存在 glibc 缓冲区溢出风险(CVE-2023-4911),已全部下线并回滚至 v2.38.0 修复版本。镜像构建遵循最小化原则:基础镜像统一使用 debian:12-slim,禁止 apt-get install -y 后未执行 apt-get clean && rm -rf /var/lib/apt/lists/*。
网络策略与服务网格隔离
通过 Istio 的 PeerAuthentication 和 DestinationRule 实现 mTLS 全链路加密,所有服务间通信强制双向证书验证。审计日志显示,某电商订单服务曾因未配置 portLevelMtls 导致 3 个 Pod 间明文传输支付令牌,已在生产环境紧急注入 sidecar.istio.io/inject: "true" 标签并重启。
权限最小化实践
Kubernetes RBAC 权限严格按角色划分:运维组仅拥有 clusterrolebinding 查看权限,应用团队无权访问 secrets 命名空间。审计发现 default ServiceAccount 曾被误赋予 cluster-admin 角色,通过以下命令批量清理:
kubectl get clusterrolebinding -o jsonpath='{range .items[?(@.subjects[0].name=="default")]}{.metadata.name}{"\n"}{end}' | xargs -I{} kubectl delete clusterrolebinding {}
审计关键发现汇总
| 问题类型 | 发生频率 | 影响等级 | 整改状态 |
|---|---|---|---|
| TLS 证书过期 | 4次/月 | 高 | 自动轮换已上线 |
| 日志未脱敏 | 12处 | 中 | 已部署 Fluentd 过滤插件 |
| etcd 未启用静态加密 | 3节点 | 极高 | 已通过 --encryption-provider-config 修复 |
应急响应流程图
graph TD
A[告警触发] --> B{是否符合SLA阈值?}
B -->|是| C[自动隔离故障Pod]
B -->|否| D[人工介入分析]
C --> E[调用Webhook通知SRE值班群]
E --> F[执行备份恢复脚本]
F --> G[验证服务健康度]
G --> H[关闭告警并归档事件]
密钥生命周期管理
所有数据库连接凭据、API 密钥均通过 HashiCorp Vault 动态生成,TTL 设为 4 小时。审计发现某遗留 Jenkins Job 直接在 JENKINS_HOME 中硬编码 AWS Access Key,已替换为 Vault Agent Sidecar 注入方式,并通过 vault kv get -field=access_key secret/aws/prod 获取凭证。
审计工具链集成
每日凌晨 2:00 自动执行 CIS Kubernetes Benchmark v1.8.0 检查,结果推送至 Grafana Dashboard。最近一次扫描暴露 kubelet --anonymous-auth=false 未生效问题,根因为 systemd unit 文件中 EnvironmentFile 覆盖了启动参数,已通过 Ansible 模板修正 /etc/default/kubelet 配置。
生产变更黄金标准
所有变更必须通过 Change Advisory Board(CAB)评审,且满足“三不原则”:不跨周末、不跨节假日、不跨业务高峰(08:00–22:00)。2024年Q2 共拒绝 7 次未经容量评估的扩缩容申请,其中 3 次因未提供 Prometheus 历史 CPU 使用率趋势图被退回。
日志留存合规性
应用日志、审计日志、网络流日志分别保留 180 天、365 天、90 天,存储于对象存储 S3 兼容服务,启用 WORM(Write Once Read Many)策略防止篡改。某次 SOC2 审计中,取证人员成功从 2023 年 11 月 17 日的 audit-2023-11-17.gz 文件中还原出异常登录行为路径。
