Files
ZiPingEightChar/scripts/_update_sig.py
T
moxiaonian 415cb472b1 v3.6.1 安全修复同步发版
- 盘面图 XSS 防护:装配层全面转义用户可控字段 + 模板严格 CSP(default-src 'none',无 script-src)+ 6 项注入向量回归测试
- 自更新签名信任锚:--apply 双源 manifest 交叉验证 → ed25519 验签(纯 Python 实现,缺 sig 即拒)→ 逐文件 SHA256,全 fail-closed
- sync 工具同步后自动重建 manifest + 签名 + 独立验签,私钥缺失 fail-closed
- 测试 170 项全绿
2026-10-10 16:21:30 +08:00

129 lines
3.8 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""纯 Python ed25519(RFC 8032)签名与验签,零依赖,仅用标准库。
self_update.py --apply 用其中的 verify() 校验 manifest.json 签名;
tools/sign_manifest.py 用 sign() 生成签名。验签性能不在关键路径上
(每次更新只验一次),实现取教科书式的扩展坐标点运算,求正确不求快。
密钥编码:私钥 = 32 字节种子(hex/base64 存储);公钥 = 32 字节 RFC 8032
标准压缩编码;签名 = 64 字节 R‖S。
"""
import hashlib
# 域参数:GF(2^255 - 19),曲线 -x² + y² = 1 + d·x²·y²
_P = 2 ** 255 - 19
_Q = 2 ** 252 + 27742317777372353535851937790883648493 # 群的阶
_D = (-121665 * pow(121666, _P - 2, _P)) % _P
_I = pow(2, (_P - 1) // 4, _P) # sqrt(-1)
def _xrecover(y: int) -> int:
xx = (y * y - 1) * pow(_D * y * y + 1, _P - 2, _P) % _P
x = pow(xx, (_P + 3) // 8, _P)
if (x * x - xx) % _P != 0:
x = x * _I % _P
if x & 1:
x = _P - x
return x
_BY = (4 * pow(5, _P - 2, _P)) % _P
_BX = _xrecover(_BY)
# 基点(扩展坐标 x, y, z, t,x=X/Z, y=Y/Z, t=XY/Z)
_B = (_BX, _BY, 1, _BX * _BY % _P)
_IDENTITY = (0, 1, 1, 0)
def _point_add(P, Q):
x1, y1, z1, t1 = P
x2, y2, z2, t2 = Q
a = (y1 - x1) * (y2 - x2) % _P
b = (y1 + x1) * (y2 + x2) % _P
c = 2 * _D * t1 * t2 % _P
d = 2 * z1 * z2 % _P
e, f, g, h = b - a, d - c, d + c, b + a
return (e * f % _P, g * h % _P, f * g % _P, e * h % _P)
def _scalarmult(P, e: int):
Q = _IDENTITY
while e > 0:
if e & 1:
Q = _point_add(Q, P)
P = _point_add(P, P)
e >>= 1
return Q
def _encode_point(P) -> bytes:
x, y, z, _t = P
zi = pow(z, _P - 2, _P)
x, y = x * zi % _P, y * zi % _P
return (y | ((x & 1) << 255)).to_bytes(32, "little")
def _decode_point(s: bytes):
if len(s) != 32:
raise ValueError("点编码长度须为 32 字节")
y = int.from_bytes(s, "little") & ((1 << 255) - 1)
if y >= _P:
raise ValueError("点编码 y 坐标越界")
x = _xrecover(y)
if (x & 1) != (s[31] >> 7):
x = _P - x
if (-x * x + y * y - 1 - _D * x * x * y * y) % _P != 0:
raise ValueError("点不在曲线上")
return (x, y, 1, x * y % _P)
def _hint(m: bytes) -> int:
return int.from_bytes(hashlib.sha512(m).digest(), "little")
def _secret_expand(seed: bytes):
if len(seed) != 32:
raise ValueError("私钥种子须为 32 字节")
h = hashlib.sha512(seed).digest()
a = int.from_bytes(h[:32], "little")
a &= (1 << 254) - 8
a |= 1 << 254
return a, h[32:]
def publickey(seed: bytes) -> bytes:
"""32 字节种子 → 32 字节公钥。"""
a, _prefix = _secret_expand(seed)
return _encode_point(_scalarmult(_B, a))
def sign(seed: bytes, msg: bytes) -> bytes:
"""RFC 8032 确定性签名:64 字节 R‖S。仅发版工具(tools/)使用。"""
a, prefix = _secret_expand(seed)
A = publickey(seed)
r = _hint(prefix + msg) % _Q
R = _encode_point(_scalarmult(_B, r))
S = (r + _hint(R + A + msg) * a) % _Q
return R + S.to_bytes(32, "little")
def verify(pk: bytes, msg: bytes, sig: bytes) -> bool:
"""验签:通过返回 True;任何不符(含畸形输入)返回 False,不抛异常。"""
if len(pk) != 32 or len(sig) != 64:
return False
R, s = sig[:32], int.from_bytes(sig[32:], "little")
if s >= _Q:
return False
try:
A = _decode_point(pk)
Rpt = _decode_point(R)
except ValueError:
return False
h = _hint(R + pk + msg) % _Q
# [S]B == R + [h]A
return _encode_point(_scalarmult(_B, s)) == _encode_point(
_point_add(Rpt, _scalarmult(A, h)))
def fingerprint(pk: bytes) -> str:
"""公钥指纹:SHA256(公钥) 十六进制,用于注释与审计对照。"""
return hashlib.sha256(pk).hexdigest()