Skip to main content

evorule_cli/
signing.rs

1// SPDX-License-Identifier: AGPL-3.0-or-later
2// Copyright (C) 2026 EvoRule Project
3// This file is part of EvoRule, licensed under GNU Affero General Public License v3 or later.
4//! 审计锚点签名工具(G-A1 真实性 / 防抵赖)—— CLI 侧最小自包含实现
5//!
6//! # 背景
7//!
8//! evorule-governance 的审计链用 BLAKE3 哈希保证"完整性/被动篡改",但不提供"真实性/防抵赖"。
9//! G-A1 在该链之上引入 **ed25519(RFC 8032)确定性签名锚点**:用私钥对审计链尾 `last_hash`
10//! 签名,公钥随导出物分发即可供第三方离线验证"审计链确由私钥持有者生成"。
11//!
12//! # 本模块定位
13//!
14//! 遵循 evorule-cli「不引入 evorule-governance」的架构约束(CLI 定位 tier0+tier1、musl 静态),
15//! 本模块**复制** governance `signing.rs` 的最小实现(与已复制的 `hash.rs` 同思路),
16//! 仅提供 CLI 需要的:密钥生成 + 验签 + hex 编解码。锚点载荷的重算在 `verify-anchors` 命令中。
17//!
18//! # 密钥纪律
19//!
20//! 私钥不落盘于审计资产/规则库,由调用方注入 32 字节种子;`generate_keys` 仅供一次性运维使用。
21
22use ed25519_dalek::{Signature, Signer, Verifier};
23
24/// 签名相关错误
25#[derive(Debug, Clone, PartialEq, Eq)]
26pub enum SignError {
27    /// 签名/验证输入非法(种子长度、验证密钥字节、签名长度、hex 解析)
28    Invalid(String),
29    /// 生成随机种子失败(OS 熵源不可用)
30    Randomness(&'static str),
31}
32
33impl core::fmt::Display for SignError {
34    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
35        match self {
36            SignError::Invalid(msg) => write!(f, "审计签名: 非法输入: {msg}"),
37            SignError::Randomness(src) => write!(f, "审计签名: 随机种子失败: {src}"),
38        }
39    }
40}
41
42impl std::error::Error for SignError {}
43
44/// ed25519 审计签名器(持有私钥)
45///
46/// 同一私钥 + 同一载荷 → 同一签名(RFC 8032 确定性 nonce,无 RNG),与 evorule 的确定性执行纪律兼容。
47#[derive(Debug, Clone)]
48pub struct AuditSigner {
49    signing: ed25519_dalek::SigningKey,
50    verifying: ed25519_dalek::VerifyingKey,
51}
52
53impl AuditSigner {
54    /// 从 32 字节私钥种子构造签名器
55    pub fn from_bytes(seed: [u8; 32]) -> Self {
56        let signing = ed25519_dalek::SigningKey::from_bytes(&seed);
57        let verifying = signing.verifying_key();
58        Self { signing, verifying }
59    }
60
61    /// 从 64 位 hex 私钥种子字符串构造签名器
62    pub fn from_hex(seed_hex: &str) -> Result<Self, SignError> {
63        let bytes = hex_decode(seed_hex)?;
64        if bytes.len() != 32 {
65            return Err(SignError::Invalid(format!(
66                "私钥种子长度 != 32: {}",
67                bytes.len()
68            )));
69        }
70        // len 已校验 == 32,无 panic-prone 拷贝
71        let mut seed = [0u8; 32];
72        for (dst, src) in seed.iter_mut().zip(bytes.iter()) {
73            *dst = *src;
74        }
75        Ok(Self::from_bytes(seed))
76    }
77
78    /// 生成一对新密钥(一次性运维操作)
79    ///
80    /// 返回 `(sk_seed_hex, pk_hex)`:`sk_seed_hex` 为 32 字节私钥种子(需私密保存),
81    /// `pk_hex` 为 32 字节压缩公钥(可公开分发供验证)。
82    pub fn generate_keys() -> Result<(String, String), SignError> {
83        let mut seed = [0u8; 32];
84        getrandom::getrandom(&mut seed)
85            .map_err(|_| SignError::Randomness("OS 熵源不可用"))?;
86        let signer = Self::from_bytes(seed);
87        Ok((hex_encode(&seed), hex_encode(&signer.verifying_bytes())))
88    }
89
90    /// 32 字节压缩公钥(供验证方使用)
91    pub fn verifying_bytes(&self) -> [u8; 32] {
92        self.verifying.to_bytes()
93    }
94
95    /// 对载荷做确定性签名,返回 64 字节签名
96    pub fn signature_bytes(&self, payload: &[u8]) -> [u8; 64] {
97        let sig: Signature = self.signing.sign(payload);
98        sig.to_bytes()
99    }
100}
101
102/// 用公钥验证签名(供导出物离线校验使用)
103///
104/// `verifying_bytes`:32 字节压缩公钥;`payload`:被签名的载荷;`sig_bytes`:64 字节签名。
105/// 返回 `Ok(true)` 表示签名有效且完整;`Ok(false)` 表示签名不匹配;`Err` 表示输入非法。
106pub fn verify_signature(
107    verifying_bytes: [u8; 32],
108    payload: &[u8],
109    sig_bytes: &[u8; 64],
110) -> Result<bool, SignError> {
111    let verifying = ed25519_dalek::VerifyingKey::from_bytes(&verifying_bytes)
112        .map_err(|e| SignError::Invalid(format!("公钥非法: {e}")))?;
113    let sig = Signature::from_bytes(sig_bytes);
114    Ok(verifying.verify(payload, &sig).is_ok())
115}
116
117/// 十六进制编码
118pub(crate) fn hex_encode(bytes: &[u8]) -> String {
119    let mut s = String::with_capacity(bytes.len() * 2);
120    for b in bytes {
121        s.push_str(&format!("{b:02x}"));
122    }
123    s
124}
125
126/// 十六进制解码
127pub(crate) fn hex_decode(hex: &str) -> Result<Vec<u8>, SignError> {
128    if hex.len() % 2 != 0 {
129        return Err(SignError::Invalid(format!("hex 长度为奇数: {}", hex.len())));
130    }
131    let mut out = Vec::with_capacity(hex.len() / 2);
132    let bytes = hex.as_bytes();
133    for i in (0..bytes.len()).step_by(2) {
134        let hi = (bytes[i] as char).to_digit(16);
135        let lo = (bytes[i + 1] as char).to_digit(16);
136        match (hi, lo) {
137            (Some(h), Some(l)) => out.push(((h << 4) | l) as u8),
138            _ => return Err(SignError::Invalid("hex 含非十六进制字符".into())),
139        }
140    }
141    Ok(out)
142}
143
144#[cfg(test)]
145mod tests {
146    #![allow(clippy::unwrap_used)]
147    use super::*;
148
149    #[test]
150    fn test_signature_is_deterministic() {
151        let signer = AuditSigner::from_bytes([7u8; 32]);
152        let payload = b"audit-anchor-payload";
153        assert_eq!(signer.signature_bytes(payload), signer.signature_bytes(payload));
154    }
155
156    #[test]
157    fn test_sign_and_verify_ok() {
158        let signer = AuditSigner::from_bytes([7u8; 32]);
159        let pk = signer.verifying_bytes();
160        let payload = b"hello-audit";
161        let sig = signer.signature_bytes(payload);
162        assert!(verify_signature(pk, payload, &sig).unwrap());
163    }
164
165    #[test]
166    fn test_verify_rejects_tampered_payload() {
167        let signer = AuditSigner::from_bytes([7u8; 32]);
168        let pk = signer.verifying_bytes();
169        let sig = signer.signature_bytes(b"hello");
170        assert!(!verify_signature(pk, b"hellO", &sig).unwrap());
171    }
172
173    #[test]
174    fn test_hex_roundtrip() {
175        let bytes = [0u8, 1, 0xff, 0x10, 0xab, 0xcd];
176        let hex = hex_encode(&bytes);
177        assert_eq!(hex_decode(&hex).unwrap(), bytes);
178        assert!(hex_decode("zz").is_err());
179        assert!(hex_decode("abc").is_err());
180    }
181
182    #[test]
183    fn test_generate_keys_produces_hex() {
184        let (sk, pk) = AuditSigner::generate_keys().unwrap();
185        assert_eq!(sk.len(), 64);
186        assert_eq!(pk.len(), 64);
187        // 公钥由私钥正确派生
188        let signer = AuditSigner::from_hex(&sk).unwrap();
189        assert_eq!(signer.verifying_bytes(), hex_decode(&pk).unwrap().as_slice());
190    }
191}