ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Ciphey A* 搜索算法重写方案:基于解码器专属节点的定向搜索实现指南

Ciphey A* 搜索算法重写方案:基于解码器专属节点的定向搜索实现指南 Ciphey A* 搜索算法重写方案基于解码器专属节点的定向搜索实现指南【免费下载链接】Ciphey⚡ Automatically decrypt encryptions without knowing the key or cipher, decode encodings, and crack hashes ⚡项目地址: https://gitcode.com/gh_mirrors/ci/CipheyCiphey 的核心能力是在不知道密钥与加密方式的前提下自动组合多个解码器逐层还原密文而src/searchers/astar.rs中的 A* 搜索算法正是决定下一步尝试哪个解码器、沿哪条路径继续搜索的引擎。本文以仓库内 docs/astar_node_with_decoder_implementation_plan.md 实现计划为主体完整讲解解码器专属节点Decoder-Specific Nodes这一 A* 重写方案如何让每个搜索节点携带下一个要尝试的解码器、如何通过Cracktrait 暴露解码器流行度参与启发式计算、如何按流行度裁剪候选节点以控制内存开销并对照当前仓库源码说明方案的落地状态与演进脉络。读完本文你将掌握 Ciphey 搜索调度层的数据结构设计、启发式公式含义与内存优化手法可直接据此理解或继续推进该模块的改造。一、背景为什么 A* 节点需要知道下一步用哪个解码器Ciphey 的搜索问题可以抽象为给定一段密文找到一条由解码器构成的路径d1 → d2 → … → dn使逐层解码后的文本通过 Athena / English 等检查器验证见 src/checkers。这是一个典型的组合搜索问题——当前 src/filtration_system/mod.rs 中filter_and_get_decoders()注册了 24 个解码器Base64、Base32、Base58 系列、Base91、Base65536、Vigenere、Binary、Hex、Morse、Atbash、Caesar、Railfence、ROT47、Z85、A1Z26、Braille、URL、Citrix CTX1、Substitution、Brainfuck、Reverse 等若每个节点都无条件展开全部解码器搜索树会以指数级膨胀。原实现中AStarNode只携带当前解码后的文本 已用解码器路径 代价节点展开时才临时拉取全部解码器逐一尝试。这种做法有两个问题展开无针对性每个节点都对全部解码器运行crack()大量无效尝试浪费算力启发式信息不足节点不记录接下来该试谁generate_heuristic只能基于路径尾部做粗略估计无法结合候选解码器自身的流行度精确排序。因此文档提出核心思路让节点本身携带next_decoder字段明确指出展开时只运行这一个解码器。这样搜索树变成每一条边绑定一个确定的解码器A* 得以用f g h对解码器选择这一决策本身进行优先级排序把算力集中到最有希望的候选上。从当前 src/searchers/astar.rs 的实现看这一思路已部分落地——真实结构体用next_decoder_name: OptionString记录下一个解码器的名称见下文第四节对比并配合get_decoder_by_name()精确定位解码器。二、整体改造蓝图文档将重写拆为五个主要步骤外加内存优化与测试更新AStarNode结构体加入下一个要使用的解码器字段在Cracktrait 中新增get_popularity()方法修改 A* 主循环依据next_decoder字段决定展开哪个解码器更新节点创建流程每次成功解码后为每个可用解码器各生成一个携带不同next_decoder的新节点让启发式函数利用解码器流行度参与评分实现内存优化策略缓解每节点多子节点带来的 open set 膨胀。下面按文档顺序逐一展开并结合仓库源码给出实现细节与现状印证。三、步骤一AStarNode结构体改造src/searchers/astar.rs3.1 目标结构文档给出的目标结构如下struct AStarNode { /// Current state containing the decoded text and path of decoders used state: DecoderResult, /// Cost so far (g) - represents the depth in the search tree /// This increases by 1 for each decoder applied cost: u32, /// Heuristic value (h) - estimated cost to reach the goal heuristic: f32, /// Total cost (f g h) used for prioritization in the queue /// Nodes with lower total_cost are explored first total_cost: f32, /// The next decoder to try when this node is expanded /// If None, all decoders should be considered (only for the initial node) next_decoder: OptionBoxdyn Crack Sync, }关键设计点state: DecoderResult保存当前文本与已用解码器路径其中text是VecStringDecoderResult定义见 src/lib.rs 附近的类型声明cost即 A* 中的g每应用一个解码器 1heuristic即 A* 中的htotal_cost即f g hopen set 按它升序出队next_decoder: OptionBoxdyn Crack Sync是本次改造的核心Some(decoder)表示展开时只尝试该解码器None仅用于初始节点表示所有解码器都要考虑。注意 trait 对象的Sync约束由于 open set 会被并发访问当前实现用DashSet与 Mutex 保护的堆见 src/searchers/astar.rs 中的ThreadSafePriorityQueue解码器对象必须可跨线程共享这与 src/filtration_system/mod.rs 中Decoders.components: VecBoxdyn Crack Sync的约束一脉相承。另外需在文件顶部补上use crate::decoders::interface::Crack;以及std::sync::Arc、crossbeam::channel::Sender等现有依赖。3.2 当前源码中的实际形态演进对照值得说明的是当前仓库的 src/searchers/astar.rs 已把该计划落地为按名称记录下一个解码器的版本struct AStarNode { state: DecoderResult, cost: u32, total_cost: f32, /// The name of the next decoder to try when this node is expanded next_decoder_name: OptionString, }对比可见三点演进用next_decoder_name: OptionString代替OptionBoxdyn Crack Sync。展开时通过 src/filtration_system/mod.rs 的get_decoder_by_name(decoder_name)过滤出唯一的解码器其单元测试test_get_decoder_by_name验证了get_decoder_by_name(Base64)恰好返回 1 个组件当前版本移除了heuristic字段仅在节点创建时用generate_heuristic算好total_cost避免存储冗余当前版本还引入了一个特殊哨兵值next_decoder_name: Some(__RESULT__.to_string())配合total_cost: -1000.0把已找到明文的结果节点放到队列最前实现并行环境下的结果即时上报。Ord/PartialOrd/PartialEq/Eq的实现保持一致cmp反向比较total_cost从而让BinaryHeap表现为最小堆——f 值越小优先级越高impl Ord for AStarNode { fn cmp(self, other: Self) - Ordering { // Reverse ordering for min-heap (lowest f value has highest priority) other .total_cost .partial_cmp(self.total_cost) .unwrap_or(Ordering::Equal) } }四、步骤二扩展Cracktrait新增get_popularity()src/decoders/interface.rs4.1 trait 目标形态为了让启发式函数能够直接读取候选解码器的流行度文档要求在Cracktrait 上新增一个方法pub trait Crack { /// This function generates a new crack trait fn new() - Self where Self: Sized; /// Crack is the function that actually does the decoding fn crack(self, text: str, checker: CheckerTypes) - CrackResult; /// Get all tags for the current decoder fn get_tags(self) - Vecstr; /// Get the name of the current decoder fn get_name(self) - str; /// Get the popularity of the decoder (a value between 0.0 and 1.0) fn get_popularity(self) - f32; }要点返回值类型为f32语义约定为 0.01.0 之间的流行度每个解码器实现该方法时直接返回其在Decoder结构体popularity字段上的值对DecoderBase64Decoder的示例实现如下impl Crack for DecoderBase64Decoder { // Existing implementations... fn get_popularity(self) - f32 { self.popularity } }4.2 仓库中的真实落地情况当前 src/decoders/interface.rs 中该 trait 已经包含get_popularity且带默认实现/// Get the popularity of the decoder (a value between 0.0 and 1.0) /// Default implementation returns the decoders popularity field fn get_popularity(self) - f32 { // This is a default implementation that will be used if the decoder // doesnt override this method. In a real implementation, each decoder // would override this to return its actual popularity value. 0.5 }而DecoderType结构体本身就持有popularity: f32字段见 src/decoders/interface.rs 顶部定义注释明确写着We get popularity by eye-balling it or using the APIs data各具体解码器在new()中给出静态取值并覆写get_popularity()例如 src/decoders/base64_decoder.rsimpl Crack for DecoderBase64Decoder { fn new() - DecoderBase64Decoder { Decoder { name: Base64, description: Base64 is a group of binary-to-text encoding schemes..., link: https://en.wikipedia.org/wiki/Base64, tags: vec![base64, base64_url, url, decoder, base], popularity: 1.0, phantom: std::marker::PhantomData, } } ... /// Gets the popularity for the current decoder fn get_popularity(self) - f32 { self.popularity } }popularity字段本身由DefaultDecoder实现兜底默认值 0.0并可在CrackResult::new(decoder, text)创建结果时随结构体一并携带。仓库变更文档 docs/changes/2024-07-10-remove-decoder-popularity.md 记录了相关演进早先helper_functions.rs中存在独立的get_decoder_popularity函数与解码器自身的popularity字段重复维护后已删除改为统一从解码器实现中读取——这正是本计划把流行度收敛到解码器本体的动机来源。五、步骤三改造 A* 主循环src/searchers/astar.rs5.1 主循环目标代码文档给出的主循环改造核心代码如下pub fn astar(input: String, result_sender: SenderOptionDecoderResult, stop: ArcAtomicBool) { // Calculate heuristic before moving input let initial_heuristic generate_heuristic(input, [], None); let initial DecoderResult { text: vec![input], path: vec![], }; // Create initial node with no next_decoder (start with any decoder) let initial_node AStarNode { state: initial, cost: 0, heuristic: initial_heuristic, total_cost: 0.0, next_decoder: None, }; // Set to track visited states to prevent cycles let mut seen_strings HashSet::new(); let mut seen_count 0; // Priority queue for open set let mut open_set BinaryHeap::new(); // Add initial node to open set open_set.push(initial_node); let mut curr_depth: u32 1; let mut prune_threshold INITIAL_PRUNE_THRESHOLD; // Main A* loop while !open_set.is_empty() !stop.load(std::sync::atomic::Ordering::Relaxed) { // Get the node with the lowest f value (total cost) let current_node open_set.pop().unwrap(); // If there is a next_decoder, use it. Otherwise, get all decoders. let decoders match current_node.next_decoder { Some(decoder) { // We used the decoder, so update its stats helper_functions::update_decoder_stats(decoder.get_name(), true); // Create a new Decoders struct with just this decoder Decoders { components: vec![decoder.clone()] } } None { // For the initial node or if no specific decoder is set, // get all available decoders get_all_decoders() } }; let athena_checker Checker::Athena::new(); let checker CheckerTypes::CheckAthena(athena_checker); let decoder_results decoders.run(current_node.state.text[0], checker); match decoder_results { MyResults::Break(res) { // Handle successful decoding // ... (existing code for handling successful decoding) } MyResults::Continue(results_vec) { for mut r in results_vec { let mut decoders_used current_node.state.path.clone(); let mut text r.unencrypted_text.take().unwrap_or_default(); // Filter out strings that cant be decoded or have been seen before text.retain(|s| { if check_if_string_cant_be_decoded(s) { // Add stats update for failed decoding update_decoder_stats(r.decoder, false); return false; } if seen_strings.insert(s.clone()) { seen_count 1; // Prune the HashSet if it gets too large if seen_count prune_threshold { // ... (existing pruning code) } true } else { false } }); if text.is_empty() { update_decoder_stats(r.decoder, false); continue; } decoders_used.push(r.clone()); // Create new nodes for each available decoder let all_available_decoders get_all_decoders(); for next_decoder in all_available_decoders.components { let cost current_node.cost 1; let heuristic generate_heuristic(text[0], decoders_used, Some(next_decoder.clone())); let total_cost cost as f32 heuristic; let new_node AStarNode { state: DecoderResult { text: text.clone(), path: decoders_used.clone(), }, cost, heuristic, total_cost, next_decoder: Some(next_decoder), }; open_set.push(new_node); } // Update decoder stats - mark as successful since it produced valid output update_decoder_stats(r.decoder, true); } } } curr_depth 1; } // If we get here, weve exhausted all possibilities without finding a solution if !stop.load(std::sync::atomic::Ordering::Relaxed) { result_sender.try_send(None).ok(); } }5.2 循环的关键行为拆解初始节点next_decoder: None因此第一次展开调用get_all_decoders()跑完全部解码器——保证单层解码即可解决的场景被首轮覆盖这与 docs/astar.md 中Initial Full Run先对所有解码器整体跑一遍避免漏掉简单的单解码器解的策略一致定向展开非初始节点Some(decoder)分支构造只含一个解码器的Decoders { components: vec![decoder.clone()] }随后decoders.run(...)只执行该解码器统计反馈展开指定解码器时先update_decoder_stats(name, true)预记一次成功统计失败路径字符串不可解码、无有效输出则记update_decoder_stats(name, false)——这些运行期统计会反过来影响后续启发式见第七节环检测与剪枝seen_stringsHashSet记录已访问文本重复文本直接丢弃seen_count超过prune_threshold当前实现中INITIAL_PRUNE_THRESHOLD PRUNE_THRESHOLD 100000见 src/searchers/astar.rs 常量定义时触发动态剪枝停止信号stop: ArcAtomicBool用于外部中断如用户 CtrlC 或--top-results模式找到足够结果主循环每次迭代前后都检查Ordering::Relaxed原子读结果上报搜索无法继续且未被外部停止时通过result_sender.try_send(None)告知上层未找到解找到明文则send(Some(DecoderResult))。当前仓库版本在此基础上进一步并行化主循环每次从 open set 用extract_batch(PARALLEL_BATCH_SIZE)批量弹出最多 10 个节点通过 Rayon 的par_iter().flat_map(expand_node)并行展开PARALLEL_BATCH_SIZE常量见 src/searchers/astar.rs再用DashSet做线程安全的 seen 集合用ThreadSafePriorityQueueMutex 包裹的BinaryHeap保证并发安全。六、步骤四节点创建流程的范式转变6.1 从单节点全展开到多节点各定向文档强调本次重写的关键变化在成功解码后的节点生成方式旧范式一次解码产生一个节点等到该节点被弹出时再尝试所有解码器展开时才做选择新范式一次解码产生 N 个节点N 可用解码器数量每个节点预先绑定一个不同的next_decoder把选择哪个解码器提前到节点入队时就定下来并让这个选择参与f值排序。// Create new nodes for each available decoder let all_available_decoders get_all_decoders(); for next_decoder in all_available_decoders.components { // Create new node with updated cost, heuristic, and next_decoder let cost current_node.cost 1; let heuristic generate_heuristic(text[0], decoders_used, Some(next_decoder.clone())); let total_cost cost as f32 heuristic; let new_node AStarNode { state: DecoderResult { text: text.clone(), path: decoders_used.clone(), }, cost, heuristic, total_cost, next_decoder: Some(next_decoder), }; // Add to open set open_set.push(new_node); }每个新节点的state.path都追加了本次成功的CrackResultdecoders_used.push(r.clone())保证即使多个子节点共享同一文本各自的解码历史也完整可回溯。这也意味着启发式计算中path.len()深度惩罚项始终反映真实解码层数。6.2 与当前源码的对照当前 src/searchers/astar.rs 的expand_node同样遵循子节点各带一个下一步解码器的思路对decoder标签解码器src/filtration_system/mod.rs 中get_decoder_tagged_decoders即DecoderFilter::include_tag(decoder)的批量展开每个结果子节点的next_decoder_name设为该解码器名对全部解码器的兜底展开每个结果子节点的next_decoder_name: Some(decoder.get_name().to_string())并在展开时跳过与路径最后一个解码器相同的解码器last_decoder.decoder decoder.get_name()则continue以及跳过紧跟在 reciprocal 解码器之后的同类解码器防止encode ↔ decode形成来回振荡的循环路径——这是文档步骤四之外、当前实现额外加入的环预防手段。七、步骤五启发式函数接入解码器流行度7.1 文档给出的目标实现启发式函数新增流行度分量直接调用next_decoder.get_popularity()pub fn generate_heuristic(_text: str, path: [CrackResult], next_decoder: OptionBoxdyn Crack Sync) - f32 { let mut base_score 0.0; // 1. Popularity component - directly use (1.0 - popularity) if let Some(decoder) next_decoder { // Use the decoders popularity via the get_popularity method base_score (1.0 - decoder.get_popularity()); } else { // If next decoder is None, add a moderate penalty base_score 0.5; } // 2. Depth penalty - exponential growth but not too aggressive base_score (0.05 * path.len() as f32).powi(2); // 3. Penalty for uncommon pairings if path.len() 1 { if let Some(previous_decoder) path.last() { if let Some(next_decoder) next_decoder { if !is_common_sequence(previous_decoder.decoder, next_decoder.get_name()) { base_score 0.25; } } } } base_score }三个分量语义流行度分量(1.0 - popularity)解码器越流行h 增量越小、越优先展开。若next_decoder为None初始节点加 0.5 的中等惩罚等价于不预设偏好深度惩罚(0.05 * depth)²随路径加深二次增长且系数较小0.05避免过早放弃深层路径——这与 docs/astar.md 中depth penalty - exponential growth but not too aggressive的设计目标一致不常见序列惩罚当路径长度 1 时检查上一个解码器 → 下一个解码器是否属于已知常见序列不属于则 0.25。is_common_sequence的仓库实现位于 src/searchers/helper_functions.rs目前收录了 Base64/Base32/Base58/Base85 之间的常见前后承接关系例如(Base64Decoder, Base32Decoder) true、(Base32Decoder, Base64Decoder) true等未命中一律返回false触发惩罚。7.2 当前实现的演进与差异当前 src/searchers/helper_functions.rs 的generate_heuristic已按该方向实现并做了强化签名完全一致pub fn generate_heuristic( text: str, path: [CrackResult], next_decoder: OptionBoxdyn Crack Sync, ) - f32 { let mut base_score 0.0; // 1. Popularity component - directly use (1.0 - popularity) if let Some(decoder) next_decoder { base_score 1.0 - decoder.get_popularity(); // Favor decoders that have produced successful outputs in this run. base_score (1.0 - get_decoder_success_rate(decoder.get_name())) * 0.25; } else { base_score 0.5; } // 2. Depth penalty - adaptive coefficient let depth_coefficient 0.05 * (1.0 (path.len() as f32 / 20.0)); base_score (depth_coefficient * path.len() as f32).powi(2); // 3. String quality component - penalize low quality strings let quality calculate_string_quality(text); base_score (1.0 - quality) * 0.5; // 4. Penalty for uncommon pairings if path.len() 1 { if let Some(previous_decoder) path.last() { if let Some(next_decoder) next_decoder { if !is_common_sequence(previous_decoder.decoder, next_decoder.get_name()) { base_score 0.25; } } } } base_score }相对文档原型的增量包括运行期成功率反馈(1.0 - get_decoder_success_rate(name)) * 0.25让本次搜索中表现好的解码器获得更低 h。get_decoder_success_rate从全局DECODER_SUCCESS_RATES: LazyMutexHashMapString, (usize, usize)中读取由update_decoder_stats累计的成功次数, 总次数未知解码器默认 0.5src/searchers/helper_functions.rs自适应深度系数depth_coefficient 0.05 * (1.0 depth/20)路径越深惩罚增长越快配合 src/searchers/astar.rs 中MAX_DEPTH 100与动态剪枝阈值实现越深越激进地裁剪字符串质量分量(1.0 - quality) * 0.5calculate_string_quality依据长度与不可见字符占比打分 3字符为 0.1 5000为 0.3否则1.0 - |len-100|/900不可见字符占比 50% 直接归零把低质量中间文本的路径压后。其单元测试test_generate_heuristic位于 src/searchers/helper_functions.rs 的#[cfg(test)]模块验证了深度惩罚随路径变长严格递增并断言深度 5 的增量约为(0.05*5)² 0.0625误差 0.1 以内可作为公式正确性的回归依据。文档 docs/astar.md 还给出了更早的乘法惩罚数学模型h(n) b * p_s * p_r * p_p * p_q * p_c可对照理解本方案从乘法罚分向加法加权演进的取舍。八、步骤六内存优化策略每成功解码一次就生成 N 个子节点会显著放大 open set 规模文档给出三种缓解策略均可直接落地到generate_heuristic之后的节点生成段。8.1 策略一低质量节点提前剪枝在创建节点前按解码器属性过滤例如只保留流行度高于阈值的解码器// Filter out decoders that are unlikely to be useful let filtered_decoders all_available_decoders.components.into_iter() .filter(|decoder| { // Filter based on decoder properties // For example, only keep decoders with popularity above a threshold decoder.get_popularity() 0.2 }) .collect::Vec_(); // Create nodes only for the filtered decoders for next_decoder in filtered_decoders { // Create new node... }结合第四节可知该策略直接受益于get_popularity()的落地——过滤条件正是基于 trait 方法完成的。filter_decoders_by_tagssrc/filtration_system/mod.rs展示了同类按属性过滤组件的既有模式可供复用。8.2 策略二Beam Search 式束宽限制限制 open set 容量超出MAX_BEAM_WIDTH时只保留 f 值最小的前MAX_BEAM_WIDTH个节点// After adding all new nodes to the open set if open_set.len() MAX_BEAM_WIDTH { // Keep only the MAX_BEAM_WIDTH most promising nodes open_set open_set.into_sorted_vec().into_iter().take(MAX_BEAM_WIDTH).collect(); }注意BinaryHeap::into_sorted_vec()返回升序数组take(MAX_BEAM_WIDTH)恰好截取 f 值最小的节点实现束搜索beam search语义。这是一种以完备性换内存的经典取舍——搜索可能因此丢失某些解但能保证最坏情况下的内存上界。8.3 策略三按文本质量动态决定展开宽度先计算中间文本质量分再据此决定考虑多少个候选解码器并按流行度降序取前 N 个// Calculate text quality let quality calculate_string_quality(text[0]); // Determine how many decoders to consider based on quality let decoder_limit if quality 0.8 { // High-quality text - consider all decoders all_available_decoders.components.len() } else if quality 0.5 { // Medium-quality text - consider top 50% of decoders by popularity all_available_decoders.components.len() / 2 } else { // Low-quality text - consider only top 25% of decoders by popularity all_available_decoders.components.len() / 4 }; // Sort decoders by popularity (highest first) let mut sorted_decoders all_available_decoders.components; sorted_decoders.sort_by(|a, b| b.get_popularity().partial_cmp(a.get_popularity()).unwrap_or(Ordering::Equal)); // Take only the top N decoders let limited_decoders sorted_decoders.into_iter().take(decoder_limit).collect::Vec_(); // Create nodes only for the limited decoders for next_decoder in limited_decoders { // Create new node... }该策略把文本看起来越像明文就越值得全面展开的直觉量化高质量文本如接近 100 字符、可打印字符占比高的中间结果全量展开低质量文本只沿最流行解码器方向走把算力集中在最有希望的路径上。calculate_string_quality已在 src/searchers/helper_functions.rs 实现并有对应测试test_calculate_string_quality_with_invisible_chars验证不可见字符占比超过 50% 时质量归零。当前仓库版本还实现了另一层动态剪枝当seen_strings.len()超过prune_threshold时清空 seen 集合并按INITIAL_PRUNE_THRESHOLD - (progress_factor * 5000)动态下调阈值progress_factor depth / MAX_DEPTH见 src/searchers/astar.rs与文档第八节的思路相互印证。九、步骤七测试方案文档要求更新既有测试以适配新节点结构并新增验证next_decoder被正确使用的用例#[test] fn astar_handles_empty_input() { // Test that A* handles empty input gracefully let (tx, rx) bounded::OptionDecoderResult(1); let stopper Arc::new(AtomicBool::new(false)); astar(.into(), tx, stopper); let result rx.recv().unwrap(); assert!(result.is_none()); } #[test] fn astar_prevents_cycles() { // Test that the algorithm doesnt revisit states // Well use a string that could potentially cause cycles let (tx, rx) bounded::OptionDecoderResult(1); let stopper Arc::new(AtomicBool::new(false)); // This is a base64 encoding of hello that when decoded and re-encoded // could potentially cause cycles if not handled properly astar(aGVsbG8.into(), tx, stopper); // The algorithm should complete without hanging let result rx.recv().unwrap(); assert!(result.is_some()); } #[test] fn astar_uses_next_decoder() { // Test that the algorithm uses the next_decoder field // Create a mock decoder that we can track // ... }当前仓库 src/searchers/astar.rs 的测试模块已包含前两个用例的实现与演进版本astar_handles_empty_input空输入应返回None与文档用例一致astar_prevents_cycles对可能引发环的输入文档用 base64 的 hello仓库实际用AAAA执行astar断言不会挂死test_parallel_astar新增的并行化验证——在独立线程中对SGVsbG8gV29ybGQHello World 的 base64运行astar断言返回结果非空且path非空。测试使用crossbeam::channel::bounded::OptionDecoderResult(1)构造容量为 1 的结果通道ArcAtomicBool构造停止信号与主函数签名完全对齐。astar_uses_next_decoder属于需要 mock 解码器进行行为追踪的用例文档以占位符呈现落地时可参照 src/decoders/crack_results.rs 测试模块中MockDecoder的写法实现Crack for DecoderMockDecoder自定义name/popularity/tags来构造可观测的解码器。十、结论与演进脉络本文档方案的目标是让 A* 搜索更聚焦把下一个尝试哪个解码器从节点展开时的临时决策前移为节点自身的固有属性并用get_popularity()暴露的流行度参与f g h排序使搜索始终优先沿最可能成功的解码器路径前进。汇总关键变更AStarNode新增next_decoder字段None仅限初始节点Cracktrait 新增get_popularity()各解码器返回自身popularity字段A* 主循环按next_decoder定向展开Some分支构造单解码器DecodersNone分支调用get_all_decoders()成功解码后为每个候选解码器各生成一个子节点各自携带不同的next_decoder启发式函数引入流行度、深度、序列常见度当前实现另加成功率与文本质量加权通过流行度过滤、束宽限制、文本质量驱动宽度三种策略控制内存增长测试覆盖空输入、环预防与next_decoder行为。对照当前仓库可确认该方案已部分落地并继续演进AStarNode采用next_decoder_name: OptionString形态配合get_decoder_by_name定向展开Crack::get_popularity已进入 src/decoders/interface.rs 并带默认实现generate_heuristic已按流行度加权并在 src/searchers/helper_functions.rs 中强化主循环则进一步演化为批量并行展开Rayon DashSetThreadSafePriorityQueue并加入__RESULT__哨兵节点与 reciprocal 防循环等机制。读者若想深入验证任一环节可从 src/searchers/astar.rs 的expand_node与 src/searchers/helper_functions.rs 的generate_heuristic入手配合各自#[cfg(test)]模块中的单元测试逐步对照仓库变更记录 docs/changes/2024-07-10-astar-refactor.md 与 docs/changes/2024-07-10-remove-decoder-popularity.md 则记录了本方案之前的关键演进步骤可作完整的背景阅读。【免费下载链接】Ciphey⚡ Automatically decrypt encryptions without knowing the key or cipher, decode encodings, and crack hashes ⚡项目地址: https://gitcode.com/gh_mirrors/ci/Ciphey创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进