Skip to content

Conversation

@Cloxl
Copy link
Owner

@Cloxl Cloxl commented Nov 24, 2025

Fixes #64

Summary by Sourcery

将签名处理从自定义 Base58 迁移到自定义 Base64 编码,并扩展 Base64 编码器以支持 x3 签名。

New Features:

  • 添加针对 x3 的 Base64 编码/解码方法,以支持使用自定义 X3_BASE64_ALPHABET 的签名。

Bug Fixes:

  • 调整 x3 前缀和头部的默认值,使其与协议中期望的值保持一致。

Enhancements:

  • 移除 Base58 编码器,并重构各客户端和加密处理器,使其统一使用 Base64 编码器。
  • 将通用的 Base64 编码/解码方法重命名为更简化的 encode/decode 接口,并相应更新调用方。

Tests:

  • 更新加密单元测试以使用新的 Base64 编码器 API,并移除已废弃的 Base58 相关断言。
Original summary in English

Summary by Sourcery

Migrate signature handling from custom Base58 to custom Base64 encoding and extend the Base64 encoder for x3 signatures.

New Features:

  • Add x3-specific Base64 encode/decode methods to support custom X3_BASE64_ALPHABET signatures.

Bug Fixes:

  • Align x3 prefix and header defaults with the expected protocol values.

Enhancements:

  • Remove the Base58 encoder and refactor clients and crypto processor to use the Base64 encoder exclusively.
  • Rename generic Base64 encode/decode methods to a simplified encode/decode interface and adjust call sites accordingly.

Tests:

  • Update crypto tests to use the new Base64 encoder API and drop obsolete Base58-related assertions.

@sourcery-ai
Copy link

sourcery-ai bot commented Nov 24, 2025

审阅者指南

重构加密编码工具,移除自定义的 Base58 实现,统一 Base64 编码器方法命名,并引入专用的 X3 Base64 编解码支持,同时更新 client、config 和测试以使用新的基于 Base64 的行为和前缀。

使用 Base64 进行 x3 签名编码/解码的时序图

sequenceDiagram
  title "Sequence diagram for x3 signature encode/decode using Base64"
  actor "Caller" as Caller
  participant "Client" as Client
  participant "CryptoProcessor" as CryptoProcessor
  participant "BitOperations" as BitOperations
  participant "Base64Encoder" as Base64Encoder

  rect rgb(230,230,250)
    Note over Caller,Base64Encoder: "Encode x3 signature (signing flow)"
    "Caller" ->> "Client": "_build_signature(d_value, a1_value, xsec_appid, string_param)"
    "Client" ->> "CryptoProcessor": "build_payload_array(d_value, a1_value, xsec_appid, string_param)"
    "CryptoProcessor" -->> "Client": "payload_array: list[int]"

    "Client" ->> "BitOperations": "xor_transform_array(payload_array)"
    "BitOperations" -->> "Client": "xor_result: list[int]"

    "Client" ->> "Base64Encoder": "encode_x3(xor_result as bytes)"
    "Base64Encoder" -->> "Client": "x3_signature_base64: str"
    "Client" -->> "Caller": "x3_signature_base64: str"
  end

  rect rgb(220,245,220)
    Note over Caller,Base64Encoder: "Decode x3 signature (verification flow)"
    "Caller" ->> "Client": "decode_x3(x3_signature: str)"
    "Client" ->> "Client": "strip_prefix(X3_PREFIX)"

    "Client" ->> "Base64Encoder": "decode_x3(x3_signature_without_prefix)"
    "Base64Encoder" -->> "Client": "decoded_bytes: bytes"

    "Client" ->> "BitOperations": "xor_transform_array(list(decoded_bytes))"
    "BitOperations" -->> "Client": "original_payload_array: list[int]"
    "Client" -->> "Caller": "original_payload_array as bytearray"
  end
Loading

文件级变更

Change Details Files
移除 Base58 编码支持,并将 x3 签名处理迁移到使用带有自定义字母表的 Base64。
  • 从编码工具模块中删除 Base58Encoder 类及其辅助方法。
  • 从加密配置类中移除与 Base58 相关的配置常量。
  • 从 CryptoProcessor 中去掉 Base58 编码器实例,并相应调整其初始化逻辑。
  • 将 x3 签名构建逻辑切换为使用 Base64 编码器的 X3 专用 encode 方法,而不再使用 Base58 编码。
  • 更新 x3 签名解码逻辑,改为使用 Base64 编码器的 X3 专用 decode 方法,并直接对解码后的字节进行操作,而不是基于 Base58 输出。
  • 放宽或移除那些断言签名和编码器输出具有 Base58 特定行为的测试。
src/xhshow/utils/encoder.py
src/xhshow/config/config.py
src/xhshow/core/crypto.py
src/xhshow/client.py
tests/test_crypto.py
标准化并扩展 Base64 编码器 API,包括通用的 encode/decode 以及 X3 专用变体,并对配置和 client 的用法进行对齐。
  • 将 Base64Encoder 方法名从 encode_to_b64/decode_from_b64 重命名为 encode/decode,以提供更简洁的 API。
  • 为 Base64Encoder 添加 encode_x3 和 decode_x3 方法,以使用专用的 X3_BASE64_ALPHABET 映射处理 X3 签名。
  • 在 CryptoConfig 中引入 X3_BASE64_ALPHABET,复用与 CUSTOM_BASE64_ALPHABET 相同的自定义 Base64 字母表字符串。
  • 更新 client 中的相关方法,使其对 xs 签名调用重命名后的 Base64Encoder.encode/decode 方法,对 x3 签名调用新的 encode_x3/decode_x3 方法。
  • 调整 client 的文档字符串,将签名描述从 Base58 改为 Base64,并澄清前缀处理方式。
  • 将 X3_PREFIX 从 mns0101_ 修改为 mns0301_,并将默认的 x4 client 头/配置值设为空字符串。
src/xhshow/utils/encoder.py
src/xhshow/config/config.py
src/xhshow/client.py
tests/test_crypto.py

针对关联 Issue 的评估

Issue Objective Addressed Explanation
#64 添加/配置新的 Base64 编码表,以替换加密配置中之前的 Base58 参数。
#64 实现新的 xs/x3 加解密逻辑,使载荷/签名的生成和解析使用更新后的基于 Base64 的算法,而非 Base58。

可能相关的 Issue

  • Feat: 小红书新版算法 #64:该 PR 移除了 Base58,引入了自定义/X3 Base64 支持,并根据新算法更新了 xs/x3 编解码逻辑和配置。

提示与命令

与 Sourcery 交互

  • 启动新审查:在 pull request 中评论 @sourcery-ai review
  • 继续讨论:直接回复 Sourcery 的审查评论。
  • 从审查评论生成 GitHub Issue:回复 Sourcery 的审查评论并请求创建 issue。你也可以在审查评论下回复 @sourcery-ai issue 来从该评论创建一个 issue。
  • 生成 pull request 标题:在 pull request 标题中任意位置写上 @sourcery-ai,即可随时生成标题。你也可以在 pull request 中评论 @sourcery-ai title 来(重新)生成标题。
  • 生成 pull request 总结:在 pull request 正文中任意位置写上 @sourcery-ai summary,即可在对应位置生成 PR 总结。你也可以在 pull request 中评论 @sourcery-ai summary 来(重新)生成总结。
  • 生成审阅者指南:在 pull request 中评论 @sourcery-ai guide,即可随时(重新)生成审阅者指南。
  • 解决所有 Sourcery 评论:在 pull request 中评论 @sourcery-ai resolve,即可将所有 Sourcery 评论标记为已解决。如果你已经处理完所有评论且不想再看到它们,这会很有用。
  • 驳回所有 Sourcery 审查:在 pull request 中评论 @sourcery-ai dismiss,即可驳回所有现有的 Sourcery 审查。特别适用于你希望从一次全新的审查开始——别忘了评论 @sourcery-ai review 来触发新的审查!

自定义你的体验

打开你的 控制面板 以:

  • 启用或禁用审查功能,例如 Sourcery 生成的 pull request 总结、审阅者指南等。
  • 更改审查语言。
  • 添加、移除或编辑自定义审查指令。
  • 调整其他审查相关设置。

获取帮助

Original review guide in English

Reviewer's Guide

Refactors the crypto encoding utilities to remove the custom Base58 implementation, standardize Base64 encoder method names, and introduce dedicated X3 Base64 encode/decode support, updating the client, config, and tests to use the new Base64-based behavior and prefixes.

Sequence diagram for x3 signature encode/decode using Base64

sequenceDiagram
  title "Sequence diagram for x3 signature encode/decode using Base64"
  actor "Caller" as Caller
  participant "Client" as Client
  participant "CryptoProcessor" as CryptoProcessor
  participant "BitOperations" as BitOperations
  participant "Base64Encoder" as Base64Encoder

  rect rgb(230,230,250)
    Note over Caller,Base64Encoder: "Encode x3 signature (signing flow)"
    "Caller" ->> "Client": "_build_signature(d_value, a1_value, xsec_appid, string_param)"
    "Client" ->> "CryptoProcessor": "build_payload_array(d_value, a1_value, xsec_appid, string_param)"
    "CryptoProcessor" -->> "Client": "payload_array: list[int]"

    "Client" ->> "BitOperations": "xor_transform_array(payload_array)"
    "BitOperations" -->> "Client": "xor_result: list[int]"

    "Client" ->> "Base64Encoder": "encode_x3(xor_result as bytes)"
    "Base64Encoder" -->> "Client": "x3_signature_base64: str"
    "Client" -->> "Caller": "x3_signature_base64: str"
  end

  rect rgb(220,245,220)
    Note over Caller,Base64Encoder: "Decode x3 signature (verification flow)"
    "Caller" ->> "Client": "decode_x3(x3_signature: str)"
    "Client" ->> "Client": "strip_prefix(X3_PREFIX)"

    "Client" ->> "Base64Encoder": "decode_x3(x3_signature_without_prefix)"
    "Base64Encoder" -->> "Client": "decoded_bytes: bytes"

    "Client" ->> "BitOperations": "xor_transform_array(list(decoded_bytes))"
    "BitOperations" -->> "Client": "original_payload_array: list[int]"
    "Client" -->> "Caller": "original_payload_array as bytearray"
  end
Loading

File-Level Changes

Change Details Files
Remove Base58 encoding support and migrate x3 signature handling to Base64 with a custom alphabet.
  • Delete the Base58Encoder class and its helper methods from the encoder utilities module.
  • Remove Base58-related configuration constants from the crypto configuration class.
  • Drop the Base58 encoder instance from the CryptoProcessor and adjust its initialization accordingly.
  • Switch x3 signature building to use the Base64 encoder’s X3-specific encode method instead of Base58 encoding.
  • Update x3 signature decoding to use the Base64 encoder’s X3-specific decode method and operate directly on decoded bytes rather than Base58 output.
  • Relax or remove tests that asserted Base58-specific behavior for signatures and encoder output.
src/xhshow/utils/encoder.py
src/xhshow/config/config.py
src/xhshow/core/crypto.py
src/xhshow/client.py
tests/test_crypto.py
Standardize and extend Base64 encoder API, including generic encode/decode and X3-specific variants, and align configuration and client usage.
  • Rename Base64Encoder methods from encode_to_b64/decode_from_b64 to encode/decode to provide a cleaner API.
  • Add encode_x3 and decode_x3 methods to Base64Encoder to handle X3 signatures using a dedicated X3_BASE64_ALPHABET mapping.
  • Introduce X3_BASE64_ALPHABET in CryptoConfig reusing the same custom Base64 alphabet string as CUSTOM_BASE64_ALPHABET.
  • Update client methods to call the renamed Base64Encoder.encode/decode methods for xs signatures and new encode_x3/decode_x3 for x3 signatures.
  • Adjust client docstrings to describe signatures as Base64 rather than Base58 and clarify prefix handling.
  • Change X3_PREFIX from mns0101_ to mns0301_ and set the default x4 client header/config value to an empty string.
src/xhshow/utils/encoder.py
src/xhshow/config/config.py
src/xhshow/client.py
tests/test_crypto.py

Assessment against linked issues

Issue Objective Addressed Explanation
#64 Add/configure the new Base64 encoding tables to replace the previous Base58 parameters in the crypto configuration.
#64 Implement the new xs/x3 encryption and decryption logic so that payload/signature generation and parsing use the updated Base64-based algorithm instead of Base58.

Possibly linked issues


Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey there - 我已经审查了你的改动,这里是一些反馈:

  • 由于 encode_to_b64/decode_from_b64 已经重命名为 encode/decode,建议保留一些轻量级、向后兼容的别名(或一个弃用层),以避免破坏现有使用该 encoder API 的下游代码。
  • CUSTOM_BASE64_ALPHABET 和 X3_BASE64_ALPHABET 当前值完全相同;建议复用同一个常量,或者在语义上更清晰地区分它们,以避免未来不小心偏离或造成困惑。
给 AI Agent 的提示词
Please address the comments from this code review:

## Overall Comments
- 由于 encode_to_b64/decode_from_b64 已经重命名为 encode/decode,建议保留一些轻量级、向后兼容的别名(或一个弃用层),以避免破坏现有使用该 encoder API 的下游代码。
- CUSTOM_BASE64_ALPHABET 和 X3_BASE64_ALPHABET 当前值完全相同;建议复用同一个常量,或者在语义上更清晰地区分它们,以避免未来不小心偏离或造成困惑。

## Individual Comments

### Comment 1
<location> `src/xhshow/utils/encoder.py:96-101` </location>
<code_context>
+            raise ValueError("Invalid Base64 input: unable to decode string") from e
+        return decoded_bytes
+
+    def encode_x3(self, input_bytes: bytes | bytearray) -> str:
+        """
+        Encode x3 signature using X3_BASE64_ALPHABET
+
+        Args:
+            input_bytes: Input byte data
+
+        Returns:
+            str: Base64 encoded string with X3 custom alphabet
+
+        Raises:
+            ValueError: Base64 encoding failed
+        """
+        try:
+            standard_encoded_bytes = base64.b64encode(input_bytes)
+            standard_encoded_string = standard_encoded_bytes.decode("utf-8")
+        except (binascii.Error, ValueError) as e:
+            raise ValueError("Failed to encode to Base64") from e
+
</code_context>

<issue_to_address>
**suggestion:** 围绕 base64.b64encode 的 try/except 很可能是不必要的,并且可能会掩盖一些意料之外的错误。

根据当前的类型标注(bytes | bytearray),base64.b64encode 在现实场景下不太可能抛出 binascii.Error 或 ValueError。这样宽泛地捕获这些异常并重新抛出一个通用的 ValueError,会在传入异常类型时掩盖编程错误。建议移除这段 try/except,或者只在 decode_x3 这条会处理外部无效输入的路径上做错误处理。

```suggestion
        standard_encoded_bytes = base64.b64encode(input_bytes)
        standard_encoded_string = standard_encoded_bytes.decode("utf-8")

```
</issue_to_address>

### Comment 2
<location> `src/xhshow/config/config.py:18-23` </location>
<code_context>
     STANDARD_BASE64_ALPHABET = (
         "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
     )
     CUSTOM_BASE64_ALPHABET = (
         "ZmserbBoHQtNP+wOcza/LpngG8yJq42KWYj0DSfdikx3VT16IlUAFM97hECvuRX5"
     )
-    BASE58_BASE = 58  # Base58 radix
-    BYTE_SIZE = 256  # Byte size (2^8)
+    X3_BASE64_ALPHABET = (
+        "ZmserbBoHQtNP+wOcza/LpngG8yJq42KWYj0DSfdikx3VT16IlUAFM97hECvuRX5"
+    )
</code_context>

<issue_to_address>
**suggestion:** CUSTOM_BASE64_ALPHABET 和 X3_BASE64_ALPHABET 目前重复了同一个字面量字符串,这存在后续偏离(drift)的风险。

既然它们当前使用完全相同的字符串,建议通过引用来定义其中一个常量(例如 X3_BASE64_ALPHABET = CUSTOM_BASE64_ALPHABET),以避免未来维护时出现差异。如果它们在概念上本来就应该不同,那么现在就给 X3_BASE64_ALPHABET 一个不同的值会更清晰。

```suggestion
    CUSTOM_BASE64_ALPHABET = (
        "ZmserbBoHQtNP+wOcza/LpngG8yJq42KWYj0DSfdikx3VT16IlUAFM97hECvuRX5"
    )
    # X3_BASE64_ALPHABET 当前使用与 CUSTOM_BASE64_ALPHABET 相同的字符表。
    # 通过引用 CUSTOM_BASE64_ALPHABET 来定义,可以避免重复和未来的偏离风险。
    X3_BASE64_ALPHABET = CUSTOM_BASE64_ALPHABET
```
</issue_to_address>

### Comment 3
<location> `tests/test_crypto.py:49-52` </location>
<code_context>
-        # 验证Base58字符
-        for char in result:
-            assert char in self.crypto.config.BASE58_ALPHABET
+        # Base58已移除,此测试不再需要
+        pass

     def test_base64_encoder(self):
</code_context>

<issue_to_address>
**suggestion (testing):** 与其保留一个对已移除 Base58 功能的空操作测试,不如重构或直接删除这个测试。

现在把这里改成了 `pass`,会产生误导,而且在 Base58 已经移除的情况下也没有任何价值。请考虑:要么删除这条测试,要么将其重用在新的 Base64 x3 行为上(例如通过 `Base64Encoder.encode_x3`/`decode_x3` 做往返编解码测试,和/或断言 x3 编码输出仅使用 `config.X3_BASE64_ALPHABET` 中的字符)。这样可以为替代 Base58 的新编码路径保持足够的测试覆盖。

```suggestion
    def test_base64_x3_encoder(self):
        """测试自定义Base64 x3 编码"""
        test_string = "Hello, World!"

        # 编码
        encoded = self.crypto.b64encoder.encode_x3(test_string)

        # 基本属性检查
        assert isinstance(encoded, str)
        assert len(encoded) > 0

        # 校验只包含自定义X3 Base64字符表中的字符
        for char in encoded:
            assert char in self.crypto.config.X3_BASE64_ALPHABET

        # 解码并验证与原始字符串一致
        decoded = self.crypto.b64encoder.decode_x3(encoded)
        assert decoded == test_string
```
</issue_to_address>

### Comment 4
<location> `tests/test_crypto.py:57` </location>
<code_context>
         """测试自定义Base64编码"""
         test_string = "Hello, World!"
-        result = self.crypto.b64encoder.encode_to_b64(test_string)
+        result = self.crypto.b64encoder.encode(test_string)

         assert isinstance(result, str)
</code_context>

<issue_to_address>
**suggestion (testing):** 请扩展 Base64 测试,以覆盖重命名方法的编解码往返以及对非法输入的处理。

测试已经改为调用 `encode`,但目前只断言了调用成功和字符表。建议同时:
- 增加一个使用 `self.crypto.b64encoder.decode(result)` 的往返检查,并断言结果等于原始 `test_string`- 增加一个测试,验证当 `decode` 处理明显非法的 Base64 输入时会抛出 `ValueError`(例如填充错误或包含非法字符的字符串)。
这会更好地覆盖重命名后的 API 及其错误处理路径。

Suggested implementation:

```python
    def test_base64_encoder(self):
        """测试自定义Base64编码"""
        test_string = "Hello, World!"

        # 调用重命名后的 encode 方法
        result = self.crypto.b64encoder.encode(test_string)

        # 结果应为字符串
        assert isinstance(result, str)

        # 编解码回原始字符串
        decoded = self.crypto.b64encoder.decode(result)
        assert decoded == test_string

    def test_base64_decoder_invalid_input(self):
        """测试Base64解码对非法输入的异常处理"""
        # 明显非法的Base64字符串(包含非法字符和错误的填充)
        invalid_inputs = [
            "!!!not_base64!!!",
            "abcd=",        # 错误填充
            "ab=c",         # 非法字符
            "YWJj*",        # 非法字符
        ]

        for invalid in invalid_inputs:
            with pytest.raises(ValueError):
                self.crypto.b64encoder.decode(invalid)

```

1. 确保本测试文件顶部已经导入 `pytest`,例如:`import pytest`。如果尚未导入,需要在文件头部添加对应的 `import`2. 此修改假设 `self.crypto.b64encoder.decode` 对非法 Base64 输入会抛出 `ValueError`。如果实际实现抛出的是其他异常类型,需要将 `pytest.raises(ValueError)` 中的异常类型调整为实际的异常类型。
</issue_to_address>

### Comment 5
<location> `tests/test_crypto.py:56-65` </location>
<code_context>
-
-        assert isinstance(result, str)
-        assert len(result) > 0
-        # 验证Base58字符
-        for char in result:
-            assert char in self.crypto.config.BASE58_ALPHABET
</code_context>

<issue_to_address>
**suggestion (testing):** 请更新签名相关测试,使其验证新的 Base64 x3 编码和解码行为。

我们已经移除了 `test_build_signature` 中与 Base58 相关的断言,但目前还没有针对新的 Base64/x3 行为做任何断言。为了在重构后保持等效的测试覆盖,建议:
-`test_build_signature` 中,断言返回的签名:
  - 在适用时具有期望的前缀(例如如果高层方法会添加 `config.X3_PREFIX`)。
  - 只包含 `self.client.crypto_processor.config.X3_BASE64_ALPHABET` 中的字符。
-`decode_x3` 添加更精确的测试,用以覆盖以下情况:
  - 一个带前缀的 x3 签名,经过 `_build_signature` + `decode_x3`(包含 XOR 过程)往返后,可以恢复原始 payload 字节。
  - 一个不带前缀的 x3 签名,以验证“前缀可选”的约定是否被遵守。
  - 一个无效的 x3 字符串(使用错误的字母表或非法 Base64),是否会从 `Base64Encoder.decode_x3` 抛出预期的 `ValueError`。
这样可以确保 Base58 → Base64 x3 的迁移在测试层面被充分覆盖,并且 `decode_x3` 的契约得到强制约束。

Suggested implementation:

```python
    def test_base58_encoder(self):
        """测试Base58编码"""

        # Base58已移除,此测试不再需要
        pass

    def test_base64_encoder(self):
        """测试自定义Base64编码 (x3 Base64)"""
        test_string = "Hello, World!"
        result = self.crypto.b64encoder.encode(test_string)

        # 返回值类型应该为字符串
        assert isinstance(result, str)
        # 签名不能为空
        assert len(result) > 0

        # 验证x3前缀及Base64字符
        config = self.crypto.config

        # 如果启用了前缀,则结果应以X3_PREFIX开头
        if getattr(config, "X3_PREFIX", None):
            if result.startswith(config.X3_PREFIX):
                core = result[len(config.X3_PREFIX) :]
            else:
                # 如果结果未带前缀,则整个结果视为核心编码
                core = result
        else:
            core = result

        # 所有字符必须在x3 Base64字母表中
        alphabet = config.X3_BASE64_ALPHABET
        for ch in core:
            assert ch in alphabet

    def test_decode_x3_round_trip_with_prefix(self):
        """测试带前缀的x3签名: _build_signature + decode_x3 之后恢复原始payload"""
        # 原始payload使用字节串, 与签名逻辑保持一致
        payload = b"test payload for x3 with prefix"
        xor_key = 0x42  # 任意非零的XOR key, 与生产/实现保持一致

        # 通过签名构建方法生成带前缀的x3签名
        # 这里假设crypto上有生成签名的方法, 名称按当前实现调整
        signature = self.crypto._build_signature(payload, xor_key=xor_key, with_prefix=True)

        config = self.crypto.config
        # 确认签名具有预期前缀
        assert signature.startswith(config.X3_PREFIX)

        # 通过decode_x3解码 (内部会处理前缀 + XOR)
        decoded = self.crypto.b64encoder.decode_x3(signature, xor_key=xor_key)

        # 解码后应恢复为原始payload
        assert isinstance(decoded, (bytes, bytearray))
        assert decoded == payload

    def test_decode_x3_round_trip_without_prefix(self):
        """测试不带前缀的x3签名: 前缀可选, 仍然可以正确解码"""
        payload = b"test payload for x3 without prefix"
        xor_key = 0x24

        # 生成不带前缀的x3签名
        raw_x3 = self.crypto.b64encoder.encode_x3(payload, xor_key=xor_key, with_prefix=False)

        config = self.crypto.config
        # 确认没有X3前缀
        assert not raw_x3.startswith(config.X3_PREFIX)

        # decode_x3 应该支持无前缀输入
        decoded = self.crypto.b64encoder.decode_x3(raw_x3, xor_key=xor_key)

        assert isinstance(decoded, (bytes, bytearray))
        assert decoded == payload

    def test_decode_x3_invalid_string_raises_value_error(self):
        """测试无效x3字符串时, Base64Encoder.decode_x3 抛出 ValueError"""
        config = self.crypto.config
        # 构造包含非法字符的x3字符串:
        # - 使用合法前缀
        # - 在核心部分注入不在X3_BASE64_ALPHABET中的字符
        bad_char = "!"  # 一般不会在Base64字母表中
        assert bad_char not in config.X3_BASE64_ALPHABET

        invalid_core = "AAAA" + bad_char + "BBBB"
        invalid_x3 = f"{config.X3_PREFIX}{invalid_core}"

        xor_key = 0x01

        # decode_x3 遇到非法字符应抛出 ValueError
        with pytest.raises(ValueError):
            self.crypto.b64encoder.decode_x3(invalid_x3, xor_key=xor_key)

```

1. 上面的实现假设:
   - `self.crypto` 暴露:
     - `config.X3_PREFIX`
     - `config.X3_BASE64_ALPHABET`
     - 方法 `_build_signature(payload: bytes, xor_key: int, with_prefix: bool = True) -> str`
     - `b64encoder.encode_x3(payload: bytes, xor_key: int, with_prefix: bool = True) -> str`
     - `b64encoder.decode_x3(x3: str, xor_key: int) -> bytes`
   如果你们当前API命名不同,请将 `_build_signature` / `encode_x3` / `decode_x3` 和参数名调整为实际实现。
2. 文件中还需要更新 `test_build_signature`:
   - 使用 `signature = self.crypto._build_signature(...)` 或对应的签名方法。
   - 若签名应始终带前缀, 断言 `signature.startswith(self.crypto.config.X3_PREFIX)`- 针对签名主体(去掉前缀后)逐字符断言 `in self.crypto.config.X3_BASE64_ALPHABET`,替换掉原来的 Base58 字母表断言。
3. 如果你们的测试用例类当前没有 `pytest` 导入, 需要在 `tests/test_crypto.py` 顶部添加 `import pytest`4. 若XOR key的来源固定在配置/常量(而不是测试中硬编码), 请将测试中的 `xor_key` 调整为与生产逻辑一致的值或从配置中读取。
</issue_to_address>

Sourcery 对开源项目免费——如果你觉得这次 Review 有帮助,欢迎点赞分享 ✨
帮我变得更有用!请在每条评论上点 👍 或 👎,我会根据你的反馈持续改进 Review 质量。
Original comment in English

Hey there - I've reviewed your changes - here's some feedback:

  • Since encode_to_b64/decode_from_b64 were renamed to encode/decode, consider keeping lightweight backward-compatible aliases (or a deprecation layer) to avoid breaking existing consumers of the encoder API.
  • CUSTOM_BASE64_ALPHABET and X3_BASE64_ALPHABET currently have identical values; consider reusing a single constant or clearly distinguishing them to avoid accidental divergence or confusion.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- Since encode_to_b64/decode_from_b64 were renamed to encode/decode, consider keeping lightweight backward-compatible aliases (or a deprecation layer) to avoid breaking existing consumers of the encoder API.
- CUSTOM_BASE64_ALPHABET and X3_BASE64_ALPHABET currently have identical values; consider reusing a single constant or clearly distinguishing them to avoid accidental divergence or confusion.

## Individual Comments

### Comment 1
<location> `src/xhshow/utils/encoder.py:96-101` </location>
<code_context>
+            raise ValueError("Invalid Base64 input: unable to decode string") from e
+        return decoded_bytes
+
+    def encode_x3(self, input_bytes: bytes | bytearray) -> str:
+        """
+        Encode x3 signature using X3_BASE64_ALPHABET
+
+        Args:
+            input_bytes: Input byte data
+
+        Returns:
+            str: Base64 encoded string with X3 custom alphabet
+
+        Raises:
+            ValueError: Base64 encoding failed
+        """
+        try:
+            standard_encoded_bytes = base64.b64encode(input_bytes)
+            standard_encoded_string = standard_encoded_bytes.decode("utf-8")
+        except (binascii.Error, ValueError) as e:
+            raise ValueError("Failed to encode to Base64") from e
+
</code_context>

<issue_to_address>
**suggestion:** The try/except around base64.b64encode is likely unnecessary and may obscure unexpected errors.

Given the type hints (bytes | bytearray), base64.b64encode should not realistically raise binascii.Error or ValueError. Catching these broadly and re-raising a generic ValueError can hide programming errors if an unexpected type is passed. Consider removing this try/except, or only using error handling on the decode_x3 path where invalid external input is expected.

```suggestion
        standard_encoded_bytes = base64.b64encode(input_bytes)
        standard_encoded_string = standard_encoded_bytes.decode("utf-8")

```
</issue_to_address>

### Comment 2
<location> `src/xhshow/config/config.py:18-23` </location>
<code_context>
     STANDARD_BASE64_ALPHABET = (
         "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
     )
     CUSTOM_BASE64_ALPHABET = (
         "ZmserbBoHQtNP+wOcza/LpngG8yJq42KWYj0DSfdikx3VT16IlUAFM97hECvuRX5"
     )
-    BASE58_BASE = 58  # Base58 radix
-    BYTE_SIZE = 256  # Byte size (2^8)
+    X3_BASE64_ALPHABET = (
+        "ZmserbBoHQtNP+wOcza/LpngG8yJq42KWYj0DSfdikx3VT16IlUAFM97hECvuRX5"
+    )
</code_context>

<issue_to_address>
**suggestion:** CUSTOM_BASE64_ALPHABET and X3_BASE64_ALPHABET duplicate the same literal, which risks drift.

Since they currently share the exact same string, consider defining one constant in terms of the other (e.g., X3_BASE64_ALPHABET = CUSTOM_BASE64_ALPHABET) to avoid future drift. If they’re intended to differ conceptually, giving X3_BASE64_ALPHABET a distinct value now would make that clearer.

```suggestion
    CUSTOM_BASE64_ALPHABET = (
        "ZmserbBoHQtNP+wOcza/LpngG8yJq42KWYj0DSfdikx3VT16IlUAFM97hECvuRX5"
    )
    # X3_BASE64_ALPHABET currently uses the same alphabet as CUSTOM_BASE64_ALPHABET.
    # Defining it in terms of CUSTOM_BASE64_ALPHABET avoids duplication and drift.
    X3_BASE64_ALPHABET = CUSTOM_BASE64_ALPHABET
```
</issue_to_address>

### Comment 3
<location> `tests/test_crypto.py:49-52` </location>
<code_context>
-        # 验证Base58字符
-        for char in result:
-            assert char in self.crypto.config.BASE58_ALPHABET
+        # Base58已移除,此测试不再需要
+        pass

     def test_base64_encoder(self):
</code_context>

<issue_to_address>
**suggestion (testing):** Repurpose or remove this test instead of leaving a no-op for removed Base58 functionality.

Leaving this as a `pass` is misleading and adds no value now that Base58 is gone. Please either delete the test or repurpose it to cover the new Base64 x3 behavior (e.g., round-trip encode/decode via `Base64Encoder.encode_x3`/`decode_x3`, and/or asserting the encoded x3 output only uses characters from `config.X3_BASE64_ALPHABET`). This will maintain coverage for the new encoding path that replaced Base58.

```suggestion
    def test_base64_x3_encoder(self):
        """测试自定义Base64 x3 编码"""
        test_string = "Hello, World!"

        # 编码
        encoded = self.crypto.b64encoder.encode_x3(test_string)

        # 基本属性检查
        assert isinstance(encoded, str)
        assert len(encoded) > 0

        # 校验只包含自定义X3 Base64字符表中的字符
        for char in encoded:
            assert char in self.crypto.config.X3_BASE64_ALPHABET

        # 解码并验证与原始字符串一致
        decoded = self.crypto.b64encoder.decode_x3(encoded)
        assert decoded == test_string
```
</issue_to_address>

### Comment 4
<location> `tests/test_crypto.py:57` </location>
<code_context>
         """测试自定义Base64编码"""
         test_string = "Hello, World!"
-        result = self.crypto.b64encoder.encode_to_b64(test_string)
+        result = self.crypto.b64encoder.encode(test_string)

         assert isinstance(result, str)
</code_context>

<issue_to_address>
**suggestion (testing):** Extend Base64 tests to cover roundtrip encode/decode and invalid input for the renamed methods.

The test now calls `encode`, but it only asserts success and the alphabet. Please also:
- Add a roundtrip check using `self.crypto.b64encoder.decode(result)` and assert it equals the original `test_string`.
- Add a test that `decode` raises `ValueError` for clearly invalid Base64 input (e.g., bad padding or invalid characters).
This will better cover the renamed API and its error handling paths.

Suggested implementation:

```python
    def test_base64_encoder(self):
        """测试自定义Base64编码"""
        test_string = "Hello, World!"

        # 调用重命名后的 encode 方法
        result = self.crypto.b64encoder.encode(test_string)

        # 结果应为字符串
        assert isinstance(result, str)

        # 编解码回原始字符串
        decoded = self.crypto.b64encoder.decode(result)
        assert decoded == test_string

    def test_base64_decoder_invalid_input(self):
        """测试Base64解码对非法输入的异常处理"""
        # 明显非法的Base64字符串(包含非法字符和错误的填充)
        invalid_inputs = [
            "!!!not_base64!!!",
            "abcd=",        # 错误填充
            "ab=c",         # 非法字符
            "YWJj*",        # 非法字符
        ]

        for invalid in invalid_inputs:
            with pytest.raises(ValueError):
                self.crypto.b64encoder.decode(invalid)

```

1. 确保本测试文件顶部已经导入 `pytest`,例如:`import pytest`。如果尚未导入,需要在文件头部添加对应的 `import`2. 此修改假设 `self.crypto.b64encoder.decode` 对非法 Base64 输入会抛出 `ValueError`。如果实际实现抛出的是其他异常类型,需要将 `pytest.raises(ValueError)` 中的异常类型调整为实际的异常类型。
</issue_to_address>

### Comment 5
<location> `tests/test_crypto.py:56-65` </location>
<code_context>
-
-        assert isinstance(result, str)
-        assert len(result) > 0
-        # 验证Base58字符
-        for char in result:
-            assert char in self.crypto.config.BASE58_ALPHABET
</code_context>

<issue_to_address>
**suggestion (testing):** Update signature tests to validate the new Base64 x3 encoding and decoding behavior.

We removed the Base58-specific assertion in `test_build_signature`, but we’re not yet asserting anything about the new Base64/x3 behavior. To keep coverage equivalent after the refactor, please:
- In `test_build_signature`, assert that the returned signature:
  - Has the expected prefix when applicable (e.g., if any higher-level method adds `config.X3_PREFIX`).
  - Contains only characters from `self.client.crypto_processor.config.X3_BASE64_ALPHABET`.
- Add targeted tests for `decode_x3` to cover:
  - A prefixed x3 signature that round-trips through `_build_signature` + `decode_x3` (after XOR), confirming the original payload bytes are recovered.
  - A non-prefixed x3 signature to verify the "prefix optional" contract.
  - An invalid x3 string (bad alphabet or invalid Base64) that results in the expected `ValueError` from `Base64Encoder.decode_x3`.
This will ensure the Base58→Base64 x3 migration is correctly exercised by tests and the `decode_x3` contract is enforced.

Suggested implementation:

```python
    def test_base58_encoder(self):
        """测试Base58编码"""

        # Base58已移除,此测试不再需要
        pass

    def test_base64_encoder(self):
        """测试自定义Base64编码 (x3 Base64)"""
        test_string = "Hello, World!"
        result = self.crypto.b64encoder.encode(test_string)

        # 返回值类型应该为字符串
        assert isinstance(result, str)
        # 签名不能为空
        assert len(result) > 0

        # 验证x3前缀及Base64字符
        config = self.crypto.config

        # 如果启用了前缀,则结果应以X3_PREFIX开头
        if getattr(config, "X3_PREFIX", None):
            if result.startswith(config.X3_PREFIX):
                core = result[len(config.X3_PREFIX) :]
            else:
                # 如果结果未带前缀,则整个结果视为核心编码
                core = result
        else:
            core = result

        # 所有字符必须在x3 Base64字母表中
        alphabet = config.X3_BASE64_ALPHABET
        for ch in core:
            assert ch in alphabet

    def test_decode_x3_round_trip_with_prefix(self):
        """测试带前缀的x3签名: _build_signature + decode_x3 之后恢复原始payload"""
        # 原始payload使用字节串, 与签名逻辑保持一致
        payload = b"test payload for x3 with prefix"
        xor_key = 0x42  # 任意非零的XOR key, 与生产/实现保持一致

        # 通过签名构建方法生成带前缀的x3签名
        # 这里假设crypto上有生成签名的方法, 名称按当前实现调整
        signature = self.crypto._build_signature(payload, xor_key=xor_key, with_prefix=True)

        config = self.crypto.config
        # 确认签名具有预期前缀
        assert signature.startswith(config.X3_PREFIX)

        # 通过decode_x3解码 (内部会处理前缀 + XOR)
        decoded = self.crypto.b64encoder.decode_x3(signature, xor_key=xor_key)

        # 解码后应恢复为原始payload
        assert isinstance(decoded, (bytes, bytearray))
        assert decoded == payload

    def test_decode_x3_round_trip_without_prefix(self):
        """测试不带前缀的x3签名: 前缀可选, 仍然可以正确解码"""
        payload = b"test payload for x3 without prefix"
        xor_key = 0x24

        # 生成不带前缀的x3签名
        raw_x3 = self.crypto.b64encoder.encode_x3(payload, xor_key=xor_key, with_prefix=False)

        config = self.crypto.config
        # 确认没有X3前缀
        assert not raw_x3.startswith(config.X3_PREFIX)

        # decode_x3 应该支持无前缀输入
        decoded = self.crypto.b64encoder.decode_x3(raw_x3, xor_key=xor_key)

        assert isinstance(decoded, (bytes, bytearray))
        assert decoded == payload

    def test_decode_x3_invalid_string_raises_value_error(self):
        """测试无效x3字符串时, Base64Encoder.decode_x3 抛出 ValueError"""
        config = self.crypto.config
        # 构造包含非法字符的x3字符串:
        # - 使用合法前缀
        # - 在核心部分注入不在X3_BASE64_ALPHABET中的字符
        bad_char = "!"  # 一般不会在Base64字母表中
        assert bad_char not in config.X3_BASE64_ALPHABET

        invalid_core = "AAAA" + bad_char + "BBBB"
        invalid_x3 = f"{config.X3_PREFIX}{invalid_core}"

        xor_key = 0x01

        # decode_x3 遇到非法字符应抛出 ValueError
        with pytest.raises(ValueError):
            self.crypto.b64encoder.decode_x3(invalid_x3, xor_key=xor_key)

```

1. 上面的实现假设:
   - `self.crypto` 暴露:
     - `config.X3_PREFIX`
     - `config.X3_BASE64_ALPHABET`
     - 方法 `_build_signature(payload: bytes, xor_key: int, with_prefix: bool = True) -> str`
     - `b64encoder.encode_x3(payload: bytes, xor_key: int, with_prefix: bool = True) -> str`
     - `b64encoder.decode_x3(x3: str, xor_key: int) -> bytes`
   如果你们当前API命名不同,请将 `_build_signature` / `encode_x3` / `decode_x3` 和参数名调整为实际实现。
2. 文件中还需要更新 `test_build_signature`:
   - 使用 `signature = self.crypto._build_signature(...)` 或对应的签名方法。
   - 若签名应始终带前缀, 断言 `signature.startswith(self.crypto.config.X3_PREFIX)`- 针对签名主体(去掉前缀后)逐字符断言 `in self.crypto.config.X3_BASE64_ALPHABET`,替换掉原来的 Base58 字母表断言。
3. 如果你们的测试用例类当前没有 `pytest` 导入, 需要在 `tests/test_crypto.py` 顶部添加 `import pytest`4. 若XOR key的来源固定在配置/常量(而不是测试中硬编码), 请将测试中的 `xor_key` 调整为与生产逻辑一致的值或从配置中读取。
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

@Cloxl Cloxl merged commit 3fc4d6d into master Dec 3, 2025
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Feat: 小红书新版算法

2 participants