Skip to main content

encryption/asymmetric/
ecdsa.rs

1// Copyright (c) 2026 erik <erik@erik.xyz> — https://erik.xyz
2
3//! ECDSA 签名 / 验签:P-256(SHA-256)、P-384(SHA-384)、secp256k1(SHA-256)。
4//!
5//! 十六进制约定(与国密 SM2 一致):
6//! - 私钥:裸标量定长 hex(P-256 / secp256k1 64 位,P-384 96 位);
7//! - 公钥:SEC1 非压缩点 `04‖X‖Y` hex(P-256 / secp256k1 130 位,P-384 194 位);
8//! - 签名:定长 `r‖s` hex(P-256 / secp256k1 128 位,P-384 192 位)。
9//!
10//! 签名 nonce 走 RFC 6979 确定性路径:`ecdsa` 0.16 的 `Signer` 实现即 RFC 6979 §3.2
11//! 算法(已在 ecdsa-0.16.9 源码确认),同私钥 + 同消息的签名逐字节可复现,
12//! 不消耗随机源(RFC 6979 附录 A.2.5 定值向量已入测试)。
13//!
14//! **low-s 归一化**:签名输出前对 `s` 取 `min(s, n−s)`(BIP-62 惯例),消除
15//! `(r, s)` / `(r, n−s)` 双解延展性——同一消息 + 密钥只有唯一签名字节串,
16//! 适合以签名字节做去重 / 缓存键的场景。RFC 6979 原始向量中 s 高于 n/2 的
17//! 条目,测试按归一化后的期望值断言(r 不变,n−s 已独立复算核对)。
18
19use rand_core::OsRng;
20
21// signature 2.2.0 在依赖图里只有一份实例,从 p256 的再导出引入一次即可
22// 供三条曲线共用(p384 / k256 的 ecdsa 模块再导出的是同一批类型)。
23use p256::ecdsa::signature::{Signer as _, Verifier as _};
24
25use crate::asymmetric::KeyPairHex;
26use crate::contract::{Identified, Signer};
27use crate::error::{Error, Result};
28use crate::internal::{hex_decode, hex_encode};
29
30/// 为 `$curve`(p256 / p384 / k256)生成一个实现 [`Signer`] 的签名器。
31macro_rules! ecdsa_signer {
32    ($(#[$doc:meta])* $name:ident, $label:literal, $identifier:literal, $curve:ident) => {
33        $(#[$doc])*
34        #[derive(Debug, Default, Clone, Copy)]
35        pub struct $name;
36
37        impl $name {
38            pub const IDENTIFIER: &'static str = $identifier;
39
40            pub fn new() -> Self {
41                Self
42            }
43
44            /// 对消息签名(RFC 6979 确定性 nonce + low-s 归一化),返回定长 `r‖s` hex。
45            fn sign_hex(message: &[u8], private_key_hex: &str) -> Result<String> {
46                let private_bytes = hex_decode(private_key_hex)
47                    .map_err(|_| Error::InvalidKey { label: $label })?;
48                let signing_key = $curve::ecdsa::SigningKey::from_slice(&private_bytes)
49                    .map_err(|_| Error::InvalidKey { label: $label })?;
50                // 标注签名类型:`SigningKey` 对 `Signer` 有多个实现(定长与 DER)。
51                // try_sign 而非 sign:签名路径理论失败返回错误,不 panic(与 SM2 同款)。
52                let signature: $curve::ecdsa::Signature = signing_key
53                    .try_sign(message)
54                    .map_err(|_| Error::SignFailed { label: $label })?;
55                // low-s 归一化(BIP-62 惯例):`(r, s)` 与 `(r, n−s)` 只保留一个。
56                let signature = signature.normalize_s().unwrap_or(signature);
57                Ok(hex_encode(signature.to_bytes().as_slice()))
58            }
59
60            /// 验签;格式错误、密钥非法、签名不对一律
61            /// [`Error::VerificationFailed`](不区分原因)。
62            fn verify_hex(
63                message: &[u8],
64                signature_hex: &str,
65                public_key_hex: &str,
66            ) -> Result<()> {
67                let public_bytes = hex_decode(public_key_hex)
68                    .map_err(|_| Error::VerificationFailed { label: $label })?;
69                let verifying_key = $curve::ecdsa::VerifyingKey::from_sec1_bytes(&public_bytes)
70                    .map_err(|_| Error::VerificationFailed { label: $label })?;
71                let signature_bytes = hex_decode(signature_hex)
72                    .map_err(|_| Error::VerificationFailed { label: $label })?;
73                let signature = $curve::ecdsa::Signature::from_slice(&signature_bytes)
74                    .map_err(|_| Error::VerificationFailed { label: $label })?;
75                verifying_key
76                    .verify(message, &signature)
77                    .map_err(|_| Error::VerificationFailed { label: $label })
78            }
79
80            /// 生成密钥对:私钥 = 标量 hex,公钥 = SEC1 非压缩点 `04‖X‖Y` hex;
81            /// 随机源为系统 CSPRNG。
82            pub fn generate_key_pair_hex() -> Result<KeyPairHex> {
83                let signing_key = $curve::ecdsa::SigningKey::random(&mut OsRng);
84                let public_key = signing_key.verifying_key().to_encoded_point(false);
85                Ok(KeyPairHex {
86                    private_key_hex: hex_encode(signing_key.to_bytes().as_slice()),
87                    public_key_hex: hex_encode(public_key.as_bytes()),
88                })
89            }
90        }
91
92        impl Identified for $name {
93            fn identifier(&self) -> &str {
94                Self::IDENTIFIER
95            }
96        }
97
98        impl Signer for $name {
99            fn sign(&self, message: &[u8], private_key_hex: &str) -> Result<String> {
100                Self::sign_hex(message, private_key_hex)
101            }
102
103            fn verify(
104                &self,
105                message: &[u8],
106                signature_hex: &str,
107                public_key_hex: &str,
108            ) -> Result<()> {
109                Self::verify_hex(message, signature_hex, public_key_hex)
110            }
111        }
112    };
113}
114
115ecdsa_signer!(
116    /// ECDSA P-256(SHA-256)签名器。
117    EcdsaP256Signer,
118    "ECDSA P-256",
119    "ecdsa-p256-sha256",
120    p256
121);
122
123ecdsa_signer!(
124    /// ECDSA P-384(SHA-384)签名器。
125    EcdsaP384Signer,
126    "ECDSA P-384",
127    "ecdsa-p384-sha384",
128    p384
129);
130
131ecdsa_signer!(
132    /// ECDSA secp256k1(SHA-256)签名器。
133    EcdsaSecp256k1Signer,
134    "ECDSA secp256k1",
135    "ecdsa-secp256k1-sha256",
136    k256
137);
138
139#[cfg(test)]
140mod tests {
141    use super::*;
142
143    /// roundtrip + 篡改必败 + 错密钥必败 + 形状检查(签名与私钥等长)。
144    fn assert_roundtrip(signer: &dyn Signer, pair: &KeyPairHex, other: &KeyPairHex) {
145        assert_eq!(
146            pair.private_key_hex.len() * 2 + 2,
147            pair.public_key_hex.len()
148        );
149        assert!(pair.public_key_hex.starts_with("04"));
150
151        let signature = signer.sign(b"payload", &pair.private_key_hex).unwrap();
152        assert_eq!(signature.len(), pair.private_key_hex.len() * 2);
153        signer
154            .verify(b"payload", &signature, &pair.public_key_hex)
155            .unwrap();
156
157        // 消息被改 → 必败。
158        let error = signer
159            .verify(b"payloaD", &signature, &pair.public_key_hex)
160            .unwrap_err();
161        assert!(matches!(error, Error::VerificationFailed { .. }));
162
163        // 换成另一把密钥的公钥 → 必败。
164        let error = signer
165            .verify(b"payload", &signature, &other.public_key_hex)
166            .unwrap_err();
167        assert!(matches!(error, Error::VerificationFailed { .. }));
168    }
169
170    #[test]
171    fn p256_roundtrip_and_hex_shapes() {
172        let pair = EcdsaP256Signer::generate_key_pair_hex().unwrap();
173        let other = EcdsaP256Signer::generate_key_pair_hex().unwrap();
174        assert_eq!(pair.private_key_hex.len(), 64);
175        assert_eq!(pair.public_key_hex.len(), 130);
176        assert_roundtrip(&EcdsaP256Signer::new(), &pair, &other);
177    }
178
179    #[test]
180    fn p384_roundtrip_and_hex_shapes() {
181        let pair = EcdsaP384Signer::generate_key_pair_hex().unwrap();
182        let other = EcdsaP384Signer::generate_key_pair_hex().unwrap();
183        assert_eq!(pair.private_key_hex.len(), 96);
184        assert_eq!(pair.public_key_hex.len(), 194);
185        assert_roundtrip(&EcdsaP384Signer::new(), &pair, &other);
186    }
187
188    #[test]
189    fn secp256k1_roundtrip_and_hex_shapes() {
190        let pair = EcdsaSecp256k1Signer::generate_key_pair_hex().unwrap();
191        let other = EcdsaSecp256k1Signer::generate_key_pair_hex().unwrap();
192        assert_eq!(pair.private_key_hex.len(), 64);
193        assert_eq!(pair.public_key_hex.len(), 130);
194        assert_roundtrip(&EcdsaSecp256k1Signer::new(), &pair, &other);
195    }
196
197    /// KAT:RFC 6979 附录 A.2.5(ECDSA 256 位 / SHA-256)。
198    /// 向量出处 RFC 6979 §A.2.5;x / Ux / Uy / (r, s) 均为 RFC 原文定值。
199    #[test]
200    fn rfc6979_p256_sample_matches_vector() {
201        const X: &str = "c9afa9d845ba75166b5c215767b1d6934e50c3db36e89b127b8a622b120f6721";
202        const PUBLIC_KEY: &str = concat!(
203            "0460fed4ba255a9d31c961eb74c6356d68c049b8923b61fa6ce669622e60f29fb6",
204            "7903fe1008b8bc99a41ae9e95628bc64f2f1b20c2d7e9f5177a3c294d4462299",
205        );
206        // RFC 原文 s = f7cb…cda8(高于 n/2);本库输出做 low-s 归一化,
207        // 期望值 = r ‖ (n − s),n−s 由独立 Python 复算核对。
208        const EXPECTED: &str = concat!(
209            "efd48b2aacb6a8fd1140dd9cd45e81d69d2c877b56aaf991c34d0ea84eaf3716",
210            "0834e36ad29a83bf2bc9385e491d6099c8fdf9d1ed67aa7ea5f51f93782857a9",
211        );
212        // RFC 向量本体(未归一化)也必须可验签——两种形态都合法。
213        const VECTOR: &str = concat!(
214            "efd48b2aacb6a8fd1140dd9cd45e81d69d2c877b56aaf991c34d0ea84eaf3716",
215            "f7cb1c942d657c41d436c7a1b6e29f65f3e900dbb9aff4064dc4ab2f843acda8",
216        );
217
218        let signer = EcdsaP256Signer::new();
219        let signature = signer.sign(b"sample", X).unwrap();
220        assert_eq!(signature, EXPECTED);
221
222        // 向量中的公钥(`04‖Ux‖Uy`)必须能验出签名(归一化前后两种形态都合法);换消息必败。
223        signer.verify(b"sample", &signature, PUBLIC_KEY).unwrap();
224        signer.verify(b"sample", VECTOR, PUBLIC_KEY).unwrap();
225        assert!(signer.verify(b"sampl3", &signature, PUBLIC_KEY).is_err());
226    }
227
228    /// KAT:RFC 6979 附录 A.2.5 的另一条定值向量(消息 "test",SHA-256)。
229    #[test]
230    fn rfc6979_p256_test_message_matches_vector() {
231        const X: &str = "c9afa9d845ba75166b5c215767b1d6934e50c3db36e89b127b8a622b120f6721";
232        const EXPECTED: &str = concat!(
233            "f1abb023518351cd71d881567b1ea663ed3efcf6c5132b354f28d3b0b7d38367",
234            "019f4113742a2b14bd25926b49c649155f267e60d3814b4c0cc84250e46f0083",
235        );
236
237        let signature = EcdsaP256Signer::new().sign(b"test", X).unwrap();
238        assert_eq!(signature, EXPECTED);
239    }
240
241    /// KAT:RFC 6979 附录 A.2.6(ECDSA 384 位 / SHA-384),消息 "sample"。
242    #[test]
243    fn rfc6979_p384_sample_matches_vector() {
244        const X: &str = concat!(
245            "6b9d3dad2e1b8c1c05b19875b6659f4de23c3b667bf297ba9aa47740787137d8",
246            "96d5724e4c70a825f872c9ea60d2edf5",
247        );
248        // RFC 原文 s 高于 n/2;本库做 low-s 归一化,期望值 = r ‖ (n − s),
249        // n−s 由独立 Python 复算核对。
250        const EXPECTED: &str = concat!(
251            "94edbb92a5ecb8aad4736e56c691916b3f88140666ce9fa73d64c4ea95ad133c",
252            "81a648152e44acf96e36dd1e80fabe46",
253            "6610b514ea0e87315e01bf24d9fcec70ecf18bf5e69dbad9a727ea302393848f",
254            "b4f04c6cd0423fdc7169525094629eab",
255        );
256
257        let signature = EcdsaP384Signer::new().sign(b"sample", X).unwrap();
258        assert_eq!(signature, EXPECTED);
259    }
260
261    /// KAT:secp256k1(RFC 6979 §3.2 确定性 k,私钥 x = 1,消息 "Satoshi Nakamoto")。
262    /// 向量出处:独立 Python(RFC 6979 §3.2 HMAC-DRBG + secp256k1 点乘)复算,
263    /// 与社区广泛使用的 Trezor 测试向量(同为 low-s 形态)逐字节一致。
264    /// RFC 6979 / RFC 5903 均未收录 secp256k1 的附录定值,这是该曲线唯一的外部锚点。
265    #[test]
266    fn rfc6979_secp256k1_satoshi_vector_matches() {
267        const X: &str = "0000000000000000000000000000000000000000000000000000000000000001";
268        const EXPECTED: &str = concat!(
269            "934b1ea10a4b3c1757e2b0c017d0b6143ce3c9a7e6a4a49860d7a6ab210ee3d8",
270            "2442ce9d2b916064108014783e923ec36b49743e2ffa1c4496f01a512aafd9e5",
271        );
272        // 私钥 1 的公钥即基点 G。
273        const PUBLIC_KEY: &str = concat!(
274            "0479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798",
275            "483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8",
276        );
277
278        let signer = EcdsaSecp256k1Signer::new();
279        let signature = signer.sign(b"Satoshi Nakamoto", X).unwrap();
280        assert_eq!(signature, EXPECTED);
281
282        signer
283            .verify(b"Satoshi Nakamoto", &signature, PUBLIC_KEY)
284            .unwrap();
285        assert!(
286            signer
287                .verify(b"Satoshi Nakamot0", &signature, PUBLIC_KEY)
288                .is_err()
289        );
290    }
291
292    /// RFC 6979 是确定性签名:同私钥 + 同消息,签名逐字节一致。
293    #[test]
294    fn signatures_are_deterministic() {
295        let pair = EcdsaP256Signer::generate_key_pair_hex().unwrap();
296        let signer = EcdsaP256Signer::new();
297        assert_eq!(
298            signer.sign(b"same", &pair.private_key_hex).unwrap(),
299            signer.sign(b"same", &pair.private_key_hex).unwrap(),
300        );
301    }
302
303    #[test]
304    fn malformed_keys_are_rejected() {
305        let pair = EcdsaP256Signer::generate_key_pair_hex().unwrap();
306        let signer = EcdsaP256Signer::new();
307
308        // 非十六进制 / 长度不对的私钥。
309        assert_eq!(
310            signer.sign(b"x", "zz").unwrap_err(),
311            Error::InvalidKey {
312                label: "ECDSA P-256"
313            }
314        );
315        assert_eq!(
316            signer.sign(b"x", "abcd").unwrap_err(),
317            Error::InvalidKey {
318                label: "ECDSA P-256"
319            }
320        );
321        // 公钥不是曲线上的点 / 签名长度不对:一律 VerificationFailed。
322        let signature = signer.sign(b"x", &pair.private_key_hex).unwrap();
323        assert_eq!(
324            signer.verify(b"x", &signature, "04deadbeef").unwrap_err(),
325            Error::VerificationFailed {
326                label: "ECDSA P-256"
327            }
328        );
329        assert_eq!(
330            signer.verify(b"x", "00", &pair.public_key_hex).unwrap_err(),
331            Error::VerificationFailed {
332                label: "ECDSA P-256"
333            }
334        );
335    }
336
337    #[test]
338    fn identifiers_are_stable() {
339        assert_eq!(EcdsaP256Signer::new().identifier(), "ecdsa-p256-sha256");
340        assert_eq!(EcdsaP384Signer::new().identifier(), "ecdsa-p384-sha384");
341        assert_eq!(
342            EcdsaSecp256k1Signer::new().identifier(),
343            "ecdsa-secp256k1-sha256"
344        );
345    }
346}